Цель исследования: - анализ поведения пользователей в мобильном приложении "Ненужные вещи"
Задачи:
1.Проанализировать связь целевого события - просмотр котатктов - и других действий пользователей;
2.Оценить, какие действия чаще совершают те пользователи, которые просматривают контакты:
Описание данных:
Датасет содержит данные о событиях, совершенных в мобильном приложении "Ненужные вещи". В нем пользователи продают свои ненужные вещи, размещая их на доске объявлений.
В датасете содержатся данные пользователей, впервые совершивших действия в приложении после 7 октября 2019 года.
!pip install plotly chart_studio
Requirement already satisfied: plotly in c:\users\tima\anaconda3\lib\site-packages (5.9.0) Requirement already satisfied: chart_studio in c:\users\tima\anaconda3\lib\site-packages (1.1.0) Requirement already satisfied: tenacity>=6.2.0 in c:\users\tima\anaconda3\lib\site-packages (from plotly) (8.0.1) Requirement already satisfied: six in c:\users\tima\anaconda3\lib\site-packages (from chart_studio) (1.16.0) Requirement already satisfied: requests in c:\users\tima\anaconda3\lib\site-packages (from chart_studio) (2.28.1) Requirement already satisfied: retrying>=1.3.3 in c:\users\tima\anaconda3\lib\site-packages (from chart_studio) (1.3.4) Requirement already satisfied: idna<4,>=2.5 in c:\users\tima\anaconda3\lib\site-packages (from requests->chart_studio) (3.3) Requirement already satisfied: charset-normalizer<3,>=2 in c:\users\tima\anaconda3\lib\site-packages (from requests->chart_studio) (2.0.4) Requirement already satisfied: certifi>=2017.4.17 in c:\users\tima\anaconda3\lib\site-packages (from requests->chart_studio) (2022.9.14) Requirement already satisfied: urllib3<1.27,>=1.21.1 in c:\users\tima\anaconda3\lib\site-packages (from requests->chart_studio) (1.26.11)
# импортируем библиотеки
import pandas as pd
import numpy as np
from scipy import stats as st
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
sns.set(rc={'figure.figsize':(16, 9)})
import datetime
import plotly.express as px
from plotly import graph_objects as go
import requests
from tqdm.auto import tqdm
import chart_studio.plotly as py
import warnings
warnings.filterwarnings('ignore')
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)
# загружаем данные
mobile_sources = pd.read_csv('https://code.s3.yandex.net/datasets/mobile_sources.csv')
mobile_dataset = pd.read_csv('https://code.s3.yandex.net/datasets/mobile_dataset.csv')
# выводим общую информацию по таблице mobile_sources
display(mobile_sources.head(10))
display(mobile_sources.describe())
display(mobile_sources.shape)
mobile_sources.info()
print('Количество дубликатов:', mobile_sources.duplicated().sum())
| userId | source | |
|---|---|---|
| 0 | 020292ab-89bc-4156-9acf-68bc2783f894 | other |
| 1 | cf7eda61-9349-469f-ac27-e5b6f5ec475c | yandex |
| 2 | 8c356c42-3ba9-4cb6-80b8-3f868d0192c3 | yandex |
| 3 | d9b06b47-0f36-419b-bbb0-3533e582a6cb | other |
| 4 | f32e1e2a-3027-4693-b793-b7b3ff274439 | |
| 5 | 17f6b2db-2964-4d11-89d8-7e38d2cb4750 | yandex |
| 6 | 62aa104f-592d-4ccb-8226-2ba0e719ded5 | yandex |
| 7 | 57321726-5d66-4d51-84f4-c797c35dcf2b | |
| 8 | c2cf55c0-95f7-4269-896c-931d14deaab5 | |
| 9 | 48e614d6-fe03-40f7-bf9e-4c4f61c19f64 | yandex |
| userId | source | |
|---|---|---|
| count | 4293 | 4293 |
| unique | 4293 | 3 |
| top | 020292ab-89bc-4156-9acf-68bc2783f894 | yandex |
| freq | 1 | 1934 |
(4293, 2)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 4293 entries, 0 to 4292 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 userId 4293 non-null object 1 source 4293 non-null object dtypes: object(2) memory usage: 67.2+ KB Количество дубликатов: 0
# выводим общую информацию по таблице mobile_dataset
display(mobile_dataset.head(10))
display(mobile_dataset.describe())
display(mobile_dataset.shape)
mobile_dataset.info()
print('Количество дубликатов:', mobile_dataset.duplicated().sum())
| event.time | event.name | user.id | |
|---|---|---|---|
| 0 | 2019-10-07 00:00:00.431357 | advert_open | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 1 | 2019-10-07 00:00:01.236320 | tips_show | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 2 | 2019-10-07 00:00:02.245341 | tips_show | cf7eda61-9349-469f-ac27-e5b6f5ec475c |
| 3 | 2019-10-07 00:00:07.039334 | tips_show | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 4 | 2019-10-07 00:00:56.319813 | advert_open | cf7eda61-9349-469f-ac27-e5b6f5ec475c |
| 5 | 2019-10-07 00:01:19.993624 | tips_show | cf7eda61-9349-469f-ac27-e5b6f5ec475c |
| 6 | 2019-10-07 00:01:27.770232 | advert_open | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 7 | 2019-10-07 00:01:34.804591 | tips_show | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 8 | 2019-10-07 00:01:49.732803 | advert_open | cf7eda61-9349-469f-ac27-e5b6f5ec475c |
| 9 | 2019-10-07 00:01:54.958298 | advert_open | 020292ab-89bc-4156-9acf-68bc2783f894 |
| event.time | event.name | user.id | |
|---|---|---|---|
| count | 74197 | 74197 | 74197 |
| unique | 74197 | 16 | 4293 |
| top | 2019-10-07 00:00:00.431357 | tips_show | cb36854f-570a-41f4-baa8-36680b396370 |
| freq | 1 | 40055 | 478 |
(74197, 3)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 74197 entries, 0 to 74196 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 event.time 74197 non-null object 1 event.name 74197 non-null object 2 user.id 74197 non-null object dtypes: object(3) memory usage: 1.7+ MB Количество дубликатов: 0
Таблица mobile_sources состоит из 2 столбцов и 4293 строк. Название столбцов:userId и source имеют тип данных object. Таблица mobile_dataset состоит из 3 столбцов и 74197 строк.Название столбцов:event.time, event.name и user.id имеют тип данных object. Количество уникальных значений в столбце user-Id совпадает в таблицах: mobile_sources и mobile_dataset, и составляет 4293. 16 видов действий учавствует в данных. 3 вида источника, с которых пользователь установил приложение. Дубликатов в таблицах нет.Необходимо изменить название столбцов и изменить тип данных в столбце event.time. В данных пропусков нет.
# меняем название столбцов в таблицах для придания результатам более читабельный вид и для объединения таблиц
mobile_sources.rename(columns = {'userId':'user_id'}, inplace = True)
mobile_dataset.rename(columns = {'user.id':'user_id', 'event.time':'time', 'event.name':'event'}, inplace = True)
display(mobile_sources.head(10))
mobile_dataset.head(10)
| user_id | source | |
|---|---|---|
| 0 | 020292ab-89bc-4156-9acf-68bc2783f894 | other |
| 1 | cf7eda61-9349-469f-ac27-e5b6f5ec475c | yandex |
| 2 | 8c356c42-3ba9-4cb6-80b8-3f868d0192c3 | yandex |
| 3 | d9b06b47-0f36-419b-bbb0-3533e582a6cb | other |
| 4 | f32e1e2a-3027-4693-b793-b7b3ff274439 | |
| 5 | 17f6b2db-2964-4d11-89d8-7e38d2cb4750 | yandex |
| 6 | 62aa104f-592d-4ccb-8226-2ba0e719ded5 | yandex |
| 7 | 57321726-5d66-4d51-84f4-c797c35dcf2b | |
| 8 | c2cf55c0-95f7-4269-896c-931d14deaab5 | |
| 9 | 48e614d6-fe03-40f7-bf9e-4c4f61c19f64 | yandex |
| time | event | user_id | |
|---|---|---|---|
| 0 | 2019-10-07 00:00:00.431357 | advert_open | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 1 | 2019-10-07 00:00:01.236320 | tips_show | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 2 | 2019-10-07 00:00:02.245341 | tips_show | cf7eda61-9349-469f-ac27-e5b6f5ec475c |
| 3 | 2019-10-07 00:00:07.039334 | tips_show | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 4 | 2019-10-07 00:00:56.319813 | advert_open | cf7eda61-9349-469f-ac27-e5b6f5ec475c |
| 5 | 2019-10-07 00:01:19.993624 | tips_show | cf7eda61-9349-469f-ac27-e5b6f5ec475c |
| 6 | 2019-10-07 00:01:27.770232 | advert_open | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 7 | 2019-10-07 00:01:34.804591 | tips_show | 020292ab-89bc-4156-9acf-68bc2783f894 |
| 8 | 2019-10-07 00:01:49.732803 | advert_open | cf7eda61-9349-469f-ac27-e5b6f5ec475c |
| 9 | 2019-10-07 00:01:54.958298 | advert_open | 020292ab-89bc-4156-9acf-68bc2783f894 |
# переводим столбец time в формат
mobile_dataset['time'] = pd.to_datetime(mobile_dataset['time'])
mobile_dataset.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 74197 entries, 0 to 74196 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 time 74197 non-null datetime64[ns] 1 event 74197 non-null object 2 user_id 74197 non-null object dtypes: datetime64[ns](1), object(2) memory usage: 1.7+ MB
#создаем столбец date
mobile_dataset['data'] = pd.to_datetime(mobile_dataset['time'].dt.date)
mobile_dataset.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 74197 entries, 0 to 74196 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 time 74197 non-null datetime64[ns] 1 event 74197 non-null object 2 user_id 74197 non-null object 3 data 74197 non-null datetime64[ns] dtypes: datetime64[ns](2), object(2) memory usage: 2.3+ MB
# заменяем неявные дубликаты в столбце event название show_contacts на contacts_show
mobile_dataset['event'] = mobile_dataset['event'].replace(['show_contacts'], 'contacts_show')
# объеденим в столбце event search_1-search_7 в search
mobile_dataset['event'] = mobile_dataset['event'].str.replace('search_\d', 'search', regex=True)
mobile_dataset['event'].value_counts()
tips_show 40055 photos_show 10012 search 6784 advert_open 6164 contacts_show 4529 map 3881 favorites_add 1417 tips_click 814 contacts_call 541 Name: event, dtype: int64
Изменили название столбцов в таблицах: mobile_dataset и mobile_sources. В таблице mobile_sources название столбца 'userId' и в таблице mobile_sources название столбца 'user.id' заменили на 'user_id'. Также в таблице mobile_dataset заменили название столбцов 'event.time' на 'time', 'event.name' на 'event'. Изменили тип данных в столбце time с object на datetime64. Добавили столбец data с типом данных datetime64.Объеденили в столбце event названия действий: show_contacts в contacts_show и search_1', 'search_2', 'search_3', 'search_4', 'search_5', 'search_6', 'search_7' в 'search'. Добавили столбцы date и week.Теперь можно объединять таблицы для анализа.
# соединяем с помощью merge данные в одну таблицу
unnecessary_things = mobile_sources.merge(mobile_dataset, on='user_id', how='left')
unnecessary_things
| user_id | source | time | event | data | |
|---|---|---|---|---|---|
| 0 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:00.431357 | advert_open | 2019-10-07 |
| 1 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:01.236320 | tips_show | 2019-10-07 |
| 2 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:07.039334 | tips_show | 2019-10-07 |
| 3 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:01:27.770232 | advert_open | 2019-10-07 |
| 4 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:01:34.804591 | tips_show | 2019-10-07 |
| ... | ... | ... | ... | ... | ... |
| 74192 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:46:47.068179 | map | 2019-11-03 | |
| 74193 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:46:58.914787 | advert_open | 2019-11-03 | |
| 74194 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:01.232230 | tips_show | 2019-11-03 | |
| 74195 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:47.475102 | advert_open | 2019-11-03 | |
| 74196 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:50.087645 | tips_show | 2019-11-03 |
74197 rows × 5 columns
# распределение событий по пользователям
print(f'Всего в логе {len(unnecessary_things)} событий.')
print(f'Всего пользователей в логе {len(unnecessary_things.user_id.unique())}.')
print(f'В среднем на пользователя приходится {int(len(unnecessary_things) / len(unnecessary_things.user_id.unique()))} событий.')
unnecessary_things.groupby('user_id')[['event']].count().describe(percentiles=[0.05, 1/4, 1/2, 3/4, 0.95, 0.99])
Всего в логе 74197 событий. Всего пользователей в логе 4293. В среднем на пользователя приходится 17 событий.
| event | |
|---|---|
| count | 4293.000000 |
| mean | 17.283252 |
| std | 29.130677 |
| min | 1.000000 |
| 5% | 3.000000 |
| 25% | 5.000000 |
| 50% | 9.000000 |
| 75% | 17.000000 |
| 95% | 59.000000 |
| 99% | 132.000000 |
| max | 478.000000 |
# строим гистограмму распределение действий по пользователям
plt.figure(figsize=(15, 7))
sns.histplot(data=unnecessary_things.groupby('user_id')[['event']].count(), x='event', kde=True)
plt.title('Распределение действий по пользователям')
plt.xlabel('Количество действий')
plt.ylabel('Количество пользователей')
plt.xlim(0,50)
plt.show()
В среднем, на каждого пользователя приходится 17 действия. Медианное количество при этом составляет 9. Минимальное значение 1, максимальное количество действий 478.
# распределение событий по источникам
print(f'Всего источников в логе {unnecessary_things.source.value_counts().count()}.')
unnecessary_things['source'].value_counts()
Всего источников в логе 3.
yandex 34286 google 20445 other 19466 Name: source, dtype: int64
# распределение источников по дням
unnecessary_things.pivot_table(
index='data', # день
columns='source', # источники переходов
values='user_id', # ID пользователей
aggfunc='nunique' # подсчёт уникальных значений
).plot(figsize=(15, 5), grid=False)
plt.title('Распределение источников по дням')
plt.xlabel('День')
plt.ylabel('Источник')
plt.show();
В течении 28 дней пользователи с разных источников заходили в приложениене. У всех источников были всплески и падения по дням. Больше всего событий проходило из источника yandex(34286). В 1,7 раза меньше google(20445) и other(19466). Заметны снижения активности пользователй в выходные дни.
# распределение событий по действиям пользователя
print(f'Всего действий в логе {unnecessary_things.event.value_counts().count()}.')
unnecessary_things['event'].value_counts()
Всего действий в логе 9.
tips_show 40055 photos_show 10012 search 6784 advert_open 6164 contacts_show 4529 map 3881 favorites_add 1417 tips_click 814 contacts_call 541 Name: event, dtype: int64
# распределение действий по дням
unnecessary_things.pivot_table(
index='data', # день
columns='event', # действия переходов
values='user_id', # ID пользователей
aggfunc='nunique' # подсчёт уникальных значений
).plot(figsize=(15, 5), grid=False)
plt.title('Распределение действий по дням')
plt.xlabel('День')
plt.ylabel('Действия')
plt.show();
# строим гистограмму по действиям и дате
data_event = unnecessary_things.groupby(['data','event']).agg({'time':'count'}).reset_index()
data_event.columns = ['data', 'event', 'count_event']
data_event.sort_values(by='count_event', ascending=False)
data_event
| data | event | count_event | |
|---|---|---|---|
| 0 | 2019-10-07 | advert_open | 401 |
| 1 | 2019-10-07 | contacts_call | 7 |
| 2 | 2019-10-07 | contacts_show | 61 |
| 3 | 2019-10-07 | favorites_add | 40 |
| 4 | 2019-10-07 | map | 168 |
| 5 | 2019-10-07 | photos_show | 230 |
| 6 | 2019-10-07 | search | 127 |
| 7 | 2019-10-07 | tips_click | 27 |
| 8 | 2019-10-07 | tips_show | 1484 |
| 9 | 2019-10-08 | advert_open | 206 |
| 10 | 2019-10-08 | contacts_call | 23 |
| 11 | 2019-10-08 | contacts_show | 119 |
| 12 | 2019-10-08 | favorites_add | 97 |
| 13 | 2019-10-08 | map | 261 |
| 14 | 2019-10-08 | photos_show | 239 |
| 15 | 2019-10-08 | search | 212 |
| 16 | 2019-10-08 | tips_click | 26 |
| 17 | 2019-10-08 | tips_show | 1316 |
| 18 | 2019-10-09 | advert_open | 130 |
| 19 | 2019-10-09 | contacts_call | 10 |
| 20 | 2019-10-09 | contacts_show | 99 |
| 21 | 2019-10-09 | favorites_add | 43 |
| 22 | 2019-10-09 | map | 133 |
| 23 | 2019-10-09 | photos_show | 235 |
| 24 | 2019-10-09 | search | 178 |
| 25 | 2019-10-09 | tips_click | 17 |
| 26 | 2019-10-09 | tips_show | 1182 |
| 27 | 2019-10-10 | advert_open | 105 |
| 28 | 2019-10-10 | contacts_call | 19 |
| 29 | 2019-10-10 | contacts_show | 106 |
| 30 | 2019-10-10 | favorites_add | 30 |
| 31 | 2019-10-10 | map | 163 |
| 32 | 2019-10-10 | photos_show | 218 |
| 33 | 2019-10-10 | search | 185 |
| 34 | 2019-10-10 | tips_click | 27 |
| 35 | 2019-10-10 | tips_show | 1390 |
| 36 | 2019-10-11 | advert_open | 109 |
| 37 | 2019-10-11 | contacts_call | 6 |
| 38 | 2019-10-11 | contacts_show | 88 |
| 39 | 2019-10-11 | favorites_add | 16 |
| 40 | 2019-10-11 | map | 108 |
| 41 | 2019-10-11 | photos_show | 381 |
| 42 | 2019-10-11 | search | 202 |
| 43 | 2019-10-11 | tips_click | 17 |
| 44 | 2019-10-11 | tips_show | 1103 |
| 45 | 2019-10-12 | advert_open | 141 |
| 46 | 2019-10-12 | contacts_call | 10 |
| 47 | 2019-10-12 | contacts_show | 54 |
| 48 | 2019-10-12 | favorites_add | 37 |
| 49 | 2019-10-12 | map | 112 |
| 50 | 2019-10-12 | photos_show | 302 |
| 51 | 2019-10-12 | search | 173 |
| 52 | 2019-10-12 | tips_click | 17 |
| 53 | 2019-10-12 | tips_show | 997 |
| 54 | 2019-10-13 | advert_open | 286 |
| 55 | 2019-10-13 | contacts_call | 4 |
| 56 | 2019-10-13 | contacts_show | 107 |
| 57 | 2019-10-13 | favorites_add | 44 |
| 58 | 2019-10-13 | map | 152 |
| 59 | 2019-10-13 | photos_show | 373 |
| 60 | 2019-10-13 | search | 194 |
| 61 | 2019-10-13 | tips_click | 11 |
| 62 | 2019-10-13 | tips_show | 1464 |
| 63 | 2019-10-14 | advert_open | 166 |
| 64 | 2019-10-14 | contacts_call | 20 |
| 65 | 2019-10-14 | contacts_show | 212 |
| 66 | 2019-10-14 | favorites_add | 49 |
| 67 | 2019-10-14 | map | 179 |
| 68 | 2019-10-14 | photos_show | 324 |
| 69 | 2019-10-14 | search | 238 |
| 70 | 2019-10-14 | tips_click | 43 |
| 71 | 2019-10-14 | tips_show | 1803 |
| 72 | 2019-10-15 | advert_open | 297 |
| 73 | 2019-10-15 | contacts_call | 32 |
| 74 | 2019-10-15 | contacts_show | 212 |
| 75 | 2019-10-15 | favorites_add | 63 |
| 76 | 2019-10-15 | map | 148 |
| 77 | 2019-10-15 | photos_show | 344 |
| 78 | 2019-10-15 | search | 241 |
| 79 | 2019-10-15 | tips_click | 25 |
| 80 | 2019-10-15 | tips_show | 1360 |
| 81 | 2019-10-16 | advert_open | 254 |
| 82 | 2019-10-16 | contacts_call | 17 |
| 83 | 2019-10-16 | contacts_show | 187 |
| 84 | 2019-10-16 | favorites_add | 21 |
| 85 | 2019-10-16 | map | 148 |
| 86 | 2019-10-16 | photos_show | 279 |
| 87 | 2019-10-16 | search | 239 |
| 88 | 2019-10-16 | tips_click | 20 |
| 89 | 2019-10-16 | tips_show | 1565 |
| 90 | 2019-10-17 | advert_open | 164 |
| 91 | 2019-10-17 | contacts_call | 35 |
| 92 | 2019-10-17 | contacts_show | 265 |
| 93 | 2019-10-17 | favorites_add | 56 |
| 94 | 2019-10-17 | map | 160 |
| 95 | 2019-10-17 | photos_show | 347 |
| 96 | 2019-10-17 | search | 187 |
| 97 | 2019-10-17 | tips_click | 42 |
| 98 | 2019-10-17 | tips_show | 1362 |
| 99 | 2019-10-18 | advert_open | 186 |
| 100 | 2019-10-18 | contacts_call | 16 |
| 101 | 2019-10-18 | contacts_show | 166 |
| 102 | 2019-10-18 | favorites_add | 35 |
| 103 | 2019-10-18 | map | 127 |
| 104 | 2019-10-18 | photos_show | 333 |
| 105 | 2019-10-18 | search | 268 |
| 106 | 2019-10-18 | tips_click | 37 |
| 107 | 2019-10-18 | tips_show | 1576 |
| 108 | 2019-10-19 | advert_open | 191 |
| 109 | 2019-10-19 | contacts_call | 20 |
| 110 | 2019-10-19 | contacts_show | 90 |
| 111 | 2019-10-19 | favorites_add | 42 |
| 112 | 2019-10-19 | map | 122 |
| 113 | 2019-10-19 | photos_show | 454 |
| 114 | 2019-10-19 | search | 231 |
| 115 | 2019-10-19 | tips_click | 9 |
| 116 | 2019-10-19 | tips_show | 1172 |
| 117 | 2019-10-20 | advert_open | 132 |
| 118 | 2019-10-20 | contacts_call | 30 |
| 119 | 2019-10-20 | contacts_show | 183 |
| 120 | 2019-10-20 | favorites_add | 50 |
| 121 | 2019-10-20 | map | 131 |
| 122 | 2019-10-20 | photos_show | 405 |
| 123 | 2019-10-20 | search | 223 |
| 124 | 2019-10-20 | tips_click | 26 |
| 125 | 2019-10-20 | tips_show | 964 |
| 126 | 2019-10-21 | advert_open | 249 |
| 127 | 2019-10-21 | contacts_call | 15 |
| 128 | 2019-10-21 | contacts_show | 175 |
| 129 | 2019-10-21 | favorites_add | 35 |
| 130 | 2019-10-21 | map | 263 |
| 131 | 2019-10-21 | photos_show | 316 |
| 132 | 2019-10-21 | search | 268 |
| 133 | 2019-10-21 | tips_click | 29 |
| 134 | 2019-10-21 | tips_show | 1558 |
| 135 | 2019-10-22 | advert_open | 164 |
| 136 | 2019-10-22 | contacts_call | 19 |
| 137 | 2019-10-22 | contacts_show | 215 |
| 138 | 2019-10-22 | favorites_add | 67 |
| 139 | 2019-10-22 | map | 149 |
| 140 | 2019-10-22 | photos_show | 358 |
| 141 | 2019-10-22 | search | 283 |
| 142 | 2019-10-22 | tips_click | 25 |
| 143 | 2019-10-22 | tips_show | 1448 |
| 144 | 2019-10-23 | advert_open | 224 |
| 145 | 2019-10-23 | contacts_call | 22 |
| 146 | 2019-10-23 | contacts_show | 239 |
| 147 | 2019-10-23 | favorites_add | 55 |
| 148 | 2019-10-23 | map | 196 |
| 149 | 2019-10-23 | photos_show | 502 |
| 150 | 2019-10-23 | search | 278 |
| 151 | 2019-10-23 | tips_click | 37 |
| 152 | 2019-10-23 | tips_show | 1808 |
| 153 | 2019-10-24 | advert_open | 244 |
| 154 | 2019-10-24 | contacts_call | 35 |
| 155 | 2019-10-24 | contacts_show | 220 |
| 156 | 2019-10-24 | favorites_add | 68 |
| 157 | 2019-10-24 | map | 163 |
| 158 | 2019-10-24 | photos_show | 378 |
| 159 | 2019-10-24 | search | 290 |
| 160 | 2019-10-24 | tips_click | 28 |
| 161 | 2019-10-24 | tips_show | 1607 |
| 162 | 2019-10-25 | advert_open | 215 |
| 163 | 2019-10-25 | contacts_call | 19 |
| 164 | 2019-10-25 | contacts_show | 188 |
| 165 | 2019-10-25 | favorites_add | 51 |
| 166 | 2019-10-25 | map | 119 |
| 167 | 2019-10-25 | photos_show | 352 |
| 168 | 2019-10-25 | search | 253 |
| 169 | 2019-10-25 | tips_click | 25 |
| 170 | 2019-10-25 | tips_show | 1396 |
| 171 | 2019-10-26 | advert_open | 274 |
| 172 | 2019-10-26 | contacts_call | 19 |
| 173 | 2019-10-26 | contacts_show | 181 |
| 174 | 2019-10-26 | favorites_add | 38 |
| 175 | 2019-10-26 | map | 141 |
| 176 | 2019-10-26 | photos_show | 577 |
| 177 | 2019-10-26 | search | 317 |
| 178 | 2019-10-26 | tips_click | 43 |
| 179 | 2019-10-26 | tips_show | 1537 |
| 180 | 2019-10-27 | advert_open | 287 |
| 181 | 2019-10-27 | contacts_call | 16 |
| 182 | 2019-10-27 | contacts_show | 101 |
| 183 | 2019-10-27 | favorites_add | 70 |
| 184 | 2019-10-27 | map | 123 |
| 185 | 2019-10-27 | photos_show | 507 |
| 186 | 2019-10-27 | search | 268 |
| 187 | 2019-10-27 | tips_click | 53 |
| 188 | 2019-10-27 | tips_show | 1436 |
| 189 | 2019-10-28 | advert_open | 305 |
| 190 | 2019-10-28 | contacts_call | 31 |
| 191 | 2019-10-28 | contacts_show | 198 |
| 192 | 2019-10-28 | favorites_add | 18 |
| 193 | 2019-10-28 | map | 226 |
| 194 | 2019-10-28 | photos_show | 332 |
| 195 | 2019-10-28 | search | 315 |
| 196 | 2019-10-28 | tips_click | 48 |
| 197 | 2019-10-28 | tips_show | 1711 |
| 198 | 2019-10-29 | advert_open | 285 |
| 199 | 2019-10-29 | contacts_call | 12 |
| 200 | 2019-10-29 | contacts_show | 162 |
| 201 | 2019-10-29 | favorites_add | 76 |
| 202 | 2019-10-29 | map | 84 |
| 203 | 2019-10-29 | photos_show | 375 |
| 204 | 2019-10-29 | search | 370 |
| 205 | 2019-10-29 | tips_click | 37 |
| 206 | 2019-10-29 | tips_show | 1795 |
| 207 | 2019-10-30 | advert_open | 237 |
| 208 | 2019-10-30 | contacts_call | 25 |
| 209 | 2019-10-30 | contacts_show | 190 |
| 210 | 2019-10-30 | favorites_add | 120 |
| 211 | 2019-10-30 | map | 68 |
| 212 | 2019-10-30 | photos_show | 313 |
| 213 | 2019-10-30 | search | 292 |
| 214 | 2019-10-30 | tips_click | 37 |
| 215 | 2019-10-30 | tips_show | 1595 |
| 216 | 2019-10-31 | advert_open | 270 |
| 217 | 2019-10-31 | contacts_call | 29 |
| 218 | 2019-10-31 | contacts_show | 234 |
| 219 | 2019-10-31 | favorites_add | 37 |
| 220 | 2019-10-31 | map | 61 |
| 221 | 2019-10-31 | photos_show | 348 |
| 222 | 2019-10-31 | search | 247 |
| 223 | 2019-10-31 | tips_click | 23 |
| 224 | 2019-10-31 | tips_show | 1619 |
| 225 | 2019-11-01 | advert_open | 228 |
| 226 | 2019-11-01 | contacts_call | 12 |
| 227 | 2019-11-01 | contacts_show | 223 |
| 228 | 2019-11-01 | favorites_add | 40 |
| 229 | 2019-11-01 | map | 72 |
| 230 | 2019-11-01 | photos_show | 305 |
| 231 | 2019-11-01 | search | 216 |
| 232 | 2019-11-01 | tips_click | 43 |
| 233 | 2019-11-01 | tips_show | 1438 |
| 234 | 2019-11-02 | advert_open | 88 |
| 235 | 2019-11-02 | contacts_call | 18 |
| 236 | 2019-11-02 | contacts_show | 122 |
| 237 | 2019-11-02 | favorites_add | 44 |
| 238 | 2019-11-02 | map | 39 |
| 239 | 2019-11-02 | photos_show | 331 |
| 240 | 2019-11-02 | search | 210 |
| 241 | 2019-11-02 | tips_click | 14 |
| 242 | 2019-11-02 | tips_show | 987 |
| 243 | 2019-11-03 | advert_open | 326 |
| 244 | 2019-11-03 | contacts_call | 20 |
| 245 | 2019-11-03 | contacts_show | 132 |
| 246 | 2019-11-03 | favorites_add | 75 |
| 247 | 2019-11-03 | map | 65 |
| 248 | 2019-11-03 | photos_show | 554 |
| 249 | 2019-11-03 | search | 279 |
| 250 | 2019-11-03 | tips_click | 28 |
| 251 | 2019-11-03 | tips_show | 1382 |
# строим столбчатую диаграмму
fig = px.bar(data_event.sort_values(by='count_event', ascending=False), # загружаем данные и заново их сортируем
y='count_event', # указываем столбец с данными для оси X
x='data', # указываем столбец с данными для оси Y
text='count_event', # добавляем аргумент, который отобразит текст с информацией
# о количестве объявлений внутри столбца графика
color = 'event'
)
# оформляем график
fig.update_layout(title='Распределение действий по дням',
xaxis_title='Дата',
yaxis_title='Количество действий')
fig.show() # выводим график
В каждом действии есть всплески по времени.Особенно сильно заметны всплески у действий: tips_show, tips_click. Больше всего событий у tips_show(40055)54% от общего количества действий, в 4 меньше photos_show(10012)13% и остальные действия составляют 24130 - 33%. Заметный спад был 12-13,19-20,26-27 октября и 2,3 ноября. Все эти дни приходятся на выходные дни, т.е в основном пользователи пользуются приложением в буднии дни. tips_show показывается всем автоматически и не зависит от его действия.
# распределение событий по дате
print(unnecessary_things['time'].describe(percentiles=[0.05, 1/4, 1/2, 3/4, 0.95, 0.99]))
print()
print(f'Минимальная дата: {unnecessary_things.time.min()}.')
print()
print(f'Максимальная дата: {unnecessary_things.time.max()}.')
print()
print(f'Период исследования: {unnecessary_things.time.max() - unnecessary_things.time.min()}.')
count 74197 unique 74197 top 2019-10-07 00:00:00.431357 freq 1 first 2019-10-07 00:00:00.431357 last 2019-11-03 23:58:12.532487 Name: time, dtype: object Минимальная дата: 2019-10-07 00:00:00.431357. Максимальная дата: 2019-11-03 23:58:12.532487. Период исследования: 27 days 23:58:12.101130.
Минимальная дата: 2019-10-07 00:00:00.431357. Максимальная дата: 2019-11-03 23:58:12.532487. Период исследования составляет практически 28 дней без 2 минут.
# отсортировываем данные по столбцам user_id time
start_end = unnecessary_things.sort_values(by=['user_id', 'time'])
start_end
| user_id | source | time | event | data | |
|---|---|---|---|---|---|
| 2171 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:39:45.989359 | tips_show | 2019-10-07 |
| 2172 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:40:31.052909 | tips_show | 2019-10-07 |
| 2173 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:41:05.722489 | tips_show | 2019-10-07 |
| 2174 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:43:20.735461 | tips_show | 2019-10-07 |
| 2175 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:45:30.917502 | tips_show | 2019-10-07 |
| ... | ... | ... | ... | ... | ... |
| 19048 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 15:51:23.959572 | tips_show | 2019-11-03 | |
| 19049 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 15:51:57.899997 | contacts_show | 2019-11-03 | |
| 19050 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 16:07:40.932077 | tips_show | 2019-11-03 | |
| 19051 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 16:08:18.202734 | tips_show | 2019-11-03 | |
| 19052 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 16:08:25.388712 | tips_show | 2019-11-03 |
74197 rows × 5 columns
# разделяем в таблице данные на начало и конец
start_end =start_end.groupby('user_id').agg(
{'time': ['min', 'max'], 'event': ['first', 'last']})
# преобразовываем MultiIndex в Index
start_end.columns = start_end.columns.map(''.join)
# меняем название столбцов
start_end = start_end.rename(columns={'timemin': 'time_min', 'timemax': 'time_max'\
, 'eventfirst': 'event_first', 'eventlast': 'event_last'}).reset_index()
# считаем количество дней для каждого пользователя
start_end['amount_time'] = start_end['time_max'] - start_end['time_min']
start_end
| user_id | time_min | time_max | event_first | event_last | amount_time | |
|---|---|---|---|---|---|---|
| 0 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-07 13:39:45.989359 | 2019-10-22 11:30:52.807203 | tips_show | tips_show | 14 days 21:51:06.817844 |
| 1 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 2019-10-19 21:34:33.849769 | 2019-11-03 17:12:09.708771 | search | contacts_show | 14 days 19:37:35.859002 |
| 2 | 00463033-5717-4bf1-91b4-09183923b9df | 2019-11-01 13:54:35.385028 | 2019-11-01 14:19:17.860053 | photos_show | photos_show | 0 days 00:24:42.475025 |
| 3 | 004690c3-5a84-4bb7-a8af-e0c8f8fca64e | 2019-10-18 22:14:05.555052 | 2019-10-31 21:42:15.606558 | search | search | 12 days 23:28:10.051506 |
| 4 | 00551e79-152e-4441-9cf7-565d7eb04090 | 2019-10-25 16:44:41.263364 | 2019-10-29 02:17:12.342406 | contacts_show | photos_show | 3 days 09:32:31.079042 |
| ... | ... | ... | ... | ... | ... | ... |
| 4288 | ffab8d8a-30bb-424a-a3ab-0b63ebbf7b07 | 2019-10-13 16:11:27.414960 | 2019-10-26 19:53:51.993545 | map | tips_show | 13 days 03:42:24.578585 |
| 4289 | ffc01466-fdb1-4460-ae94-e800f52eb136 | 2019-10-07 20:32:49.997044 | 2019-10-07 20:33:42.135500 | photos_show | contacts_show | 0 days 00:00:52.138456 |
| 4290 | ffcf50d9-293c-4254-8243-4890b030b238 | 2019-10-23 11:51:35.199237 | 2019-10-23 11:52:55.577369 | tips_show | map | 0 days 00:01:20.378132 |
| 4291 | ffe68f10-e48e-470e-be9b-eeb93128ff1a | 2019-10-21 16:39:33.867145 | 2019-10-28 07:17:52.646652 | search | photos_show | 6 days 14:38:18.779507 |
| 4292 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-10-12 00:57:21.241896 | 2019-11-03 16:08:25.388712 | tips_show | tips_show | 22 days 15:11:04.146816 |
4293 rows × 6 columns
# первые действия по уникальным пользователям
start_end.groupby('event_first').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False).sort_values(by='user_id', ascending=False)
| user_id | |
|---|---|
| event_first | |
| tips_show | 1398 |
| search | 1160 |
| map | 807 |
| photos_show | 560 |
| contacts_show | 179 |
| advert_open | 127 |
| favorites_add | 49 |
| tips_click | 13 |
# строим распределение последних действий по пользователям
start_end.groupby('event_first').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False).plot(figsize=(15, 5), grid=True)
plt.title('Распределение последних действий по пользователям')
plt.xlabel('Последних действия')
plt.ylabel('Количество пользователей')
plt.show();
В первых действиях нет contact_call. Количество действий снижается от tips_show до tips_clik. Особенно резкое снижение идет от tips_show(1368) до contacts_show(173).
# последние действия по уникальным пользователям
start_end.groupby('event_last').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False)
| user_id | |
|---|---|
| event_last | |
| tips_show | 2433 |
| photos_show | 752 |
| search | 338 |
| contacts_show | 275 |
| advert_open | 161 |
| map | 147 |
| contacts_call | 86 |
| favorites_add | 75 |
| tips_click | 26 |
start_end.groupby('event_last').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False).sum()
user_id 4293 dtype: int64
# строим распределение последних действий по пользователям
start_end.groupby('event_last').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False).plot(figsize=(15, 5), grid=True)
plt.title('Распределение последних действий по пользователям')
plt.xlabel('Последних действия')
plt.ylabel('Количество пользователей')
plt.show();
Последнии действия все 9. Количество действий также снижается от tips_show(2403)57% от общего количества пользователей до tips_clik(26)0,6%, но не так резко как первых действиях. Порядок последних действий пользователей меняется по сравнению с первыми действиями.
Построим воронку и узнаем какой процент пользователей через поисковую систему дошел до ЦС.
# выбираем уникальных пользователей в датасете по search(поиск по сайту)
search_event = unnecessary_things.query('event == "search"')
search_uniq = search_event['user_id'].unique().tolist()
# выбираем пользователей, которые карту объявления
search_user = unnecessary_things.query('user_id == @ search_uniq')
# выбираем события , по которым будем строить воронку
search_tips_show_contacts_show = search_user.query('event == "search" or event == "tips_show"or event == "contacts_show"')
# собираем количество уникальных пользователей по этапам
funnel_search = search_tips_show_contacts_show.groupby('event').agg({'user_id': 'nunique'}).sort_values(by = 'user_id', ascending=False).reset_index()
# считаем конверсию по уникальным пользователям
funnel_search['conversion'] = round(funnel_search['user_id']/ funnel_search['user_id'].sum()*100,2)
# считаем какая доля пользователей дошла до ЦС
funnel_search['funnel'] = round(funnel_search['user_id'] / funnel_search['user_id'].shift(1)*100,2)
funnel_search
| event | user_id | conversion | funnel | |
|---|---|---|---|---|
| 0 | search | 1666 | 58.58 | NaN |
| 1 | tips_show | 801 | 28.16 | 48.08 |
| 2 | contacts_show | 377 | 13.26 | 47.07 |
unnecessary_things
| user_id | source | time | event | data | |
|---|---|---|---|---|---|
| 0 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:00.431357 | advert_open | 2019-10-07 |
| 1 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:01.236320 | tips_show | 2019-10-07 |
| 2 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:07.039334 | tips_show | 2019-10-07 |
| 3 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:01:27.770232 | advert_open | 2019-10-07 |
| 4 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:01:34.804591 | tips_show | 2019-10-07 |
| ... | ... | ... | ... | ... | ... |
| 74192 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:46:47.068179 | map | 2019-11-03 | |
| 74193 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:46:58.914787 | advert_open | 2019-11-03 | |
| 74194 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:01.232230 | tips_show | 2019-11-03 | |
| 74195 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:47.475102 | advert_open | 2019-11-03 | |
| 74196 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:50.087645 | tips_show | 2019-11-03 |
74197 rows × 5 columns
# строим воронку
fig = go.Figure()
fig.add_trace(go.Funnel(
x = funnel_search['user_id'],
y = funnel_search['event'],
opacity = 0.6,
textposition = "auto",
textinfo = "value+percent previous"
))
fig.update_layout(title='Воронка по пользователям, которые пришли через поиск по приложению', title_x = 0.5)
fig.show()
После поиска по сайту , увидили рекомендованные объявления 48% пользователей. И 47 % пользователей доходят до просмотра контактов. Конверсия в Цeлевое действие search(1666) - contacts_show(377) составляет 22.63%.
Построим воронку и узнаем сколько пользователей дошло до просмотра контактов через рекомендованные объявления.
# выбираем уникальных пользователей в датасете по tips_click(кликнул рекомендованное объявление)
tips_click = unnecessary_things.query('event == "tips_click"')
tips_click_uniq = tips_click['user_id'].unique().tolist()
# выбираем пользователей, которые карту объявления
tips_click_user = unnecessary_things.query('user_id == @ tips_click_uniq')
# выбираем события , по которым будем строить воронку
tips_click_tips_show_contacts_show = tips_click_user.query('event == "tips_click" or event == "contacts_show"')
# собираем количество уникальных пользователей по этапам
funnel_tips_click = tips_click_tips_show_contacts_show.groupby('event').agg({'user_id': 'nunique'}).sort_values(by = 'user_id', ascending=False).reset_index()
# считаем конверсию по уникальным пользователям
funnel_tips_click['conversion'] = round(funnel_tips_click['user_id']/ funnel_tips_click['user_id'].sum()*100,2)
# считаем какая доля пользователей дошла до ЦС
funnel_tips_click['funnel'] = round(funnel_tips_click['user_id'] / funnel_tips_click['user_id'].shift(1)*100,2)
funnel_tips_click
| event | user_id | conversion | funnel | |
|---|---|---|---|---|
| 0 | tips_click | 322 | 76.3 | NaN |
| 1 | contacts_show | 100 | 23.7 | 31.06 |
# строим воронку
fig = go.Figure()
fig.add_trace(go.Funnel(
x = funnel_tips_click['user_id'],
y = funnel_tips_click['event'],
opacity = 0.6,
textposition = "auto",
textinfo = "value+percent previous"
))
fig.update_layout(title='Воронка по пользователям, которые пришли через рекомендованные объявления', title_x = 0.5)
fig.show()
Через рекомендованные объявления(322) до просмотра контактов доходят 100(31%) пользователей.
Retention Rate, или коэффициент удержания, показывает, как долго клиенты остаются с компанией. Это один из важнейших показателей «здоровья» бизнеса.
# создаем таблицу с первыми посещениями в приложении
first_date = unnecessary_things.groupby(['user_id'])['data'].min()
first_date.name = 'first_date'
retention_rate = unnecessary_things.join(first_date,on='user_id')
retention_rate
| user_id | source | time | event | data | first_date | |
|---|---|---|---|---|---|---|
| 0 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:00.431357 | advert_open | 2019-10-07 | 2019-10-07 |
| 1 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:01.236320 | tips_show | 2019-10-07 | 2019-10-07 |
| 2 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:07.039334 | tips_show | 2019-10-07 | 2019-10-07 |
| 3 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:01:27.770232 | advert_open | 2019-10-07 | 2019-10-07 |
| 4 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:01:34.804591 | tips_show | 2019-10-07 | 2019-10-07 |
| ... | ... | ... | ... | ... | ... | ... |
| 74192 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:46:47.068179 | map | 2019-11-03 | 2019-11-03 | |
| 74193 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:46:58.914787 | advert_open | 2019-11-03 | 2019-11-03 | |
| 74194 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:01.232230 | tips_show | 2019-11-03 | 2019-11-03 | |
| 74195 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:47.475102 | advert_open | 2019-11-03 | 2019-11-03 | |
| 74196 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:50.087645 | tips_show | 2019-11-03 | 2019-11-03 |
74197 rows × 6 columns
Получим день начала недели, за которую произошло событие. Он станет идентификатором недели.
# параметр unit метода pd.to_timedelta задаёт единицу измерения — в нашем случае дня: unit='d'. Вычтем из даты порядковый номер дня:
retention_rate['activity_week'] = pd.to_datetime(retention_rate['data'],
unit='d') - pd.to_timedelta(retention_rate['data'].dt.dayofweek, unit='d')
retention_rate['first_activity_week'] = pd.to_datetime(retention_rate['first_date'],
unit='d') - pd.to_timedelta(retention_rate['first_date'].dt.dayofweek, unit='d')
# рассчитаем lifetime пользователя в рамках когорты.
retention_rate['lifetime'] = retention_rate['activity_week'] - retention_rate['first_activity_week']
retention_rate.sort_values(by= 'lifetime', ascending=False)
retention_rate['lifetime'] = retention_rate['lifetime'] / np.timedelta64(1,'W')
retention_rate.sort_values(by= 'lifetime', ascending=False)
retention_rate['lifetime'] = retention_rate['lifetime'].astype(int)
retention_rate
| user_id | source | time | event | data | first_date | activity_week | first_activity_week | lifetime | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:00.431357 | advert_open | 2019-10-07 | 2019-10-07 | 2019-10-07 | 2019-10-07 | 0 |
| 1 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:01.236320 | tips_show | 2019-10-07 | 2019-10-07 | 2019-10-07 | 2019-10-07 | 0 |
| 2 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:00:07.039334 | tips_show | 2019-10-07 | 2019-10-07 | 2019-10-07 | 2019-10-07 | 0 |
| 3 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:01:27.770232 | advert_open | 2019-10-07 | 2019-10-07 | 2019-10-07 | 2019-10-07 | 0 |
| 4 | 020292ab-89bc-4156-9acf-68bc2783f894 | other | 2019-10-07 00:01:34.804591 | tips_show | 2019-10-07 | 2019-10-07 | 2019-10-07 | 2019-10-07 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 74192 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:46:47.068179 | map | 2019-11-03 | 2019-11-03 | 2019-10-28 | 2019-10-28 | 0 | |
| 74193 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:46:58.914787 | advert_open | 2019-11-03 | 2019-11-03 | 2019-10-28 | 2019-10-28 | 0 | |
| 74194 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:01.232230 | tips_show | 2019-11-03 | 2019-11-03 | 2019-10-28 | 2019-10-28 | 0 | |
| 74195 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:47.475102 | advert_open | 2019-11-03 | 2019-11-03 | 2019-10-28 | 2019-10-28 | 0 | |
| 74196 | d157bffc-264d-4464-8220-1cc0c42f43a9 | 2019-11-03 23:47:50.087645 | tips_show | 2019-11-03 | 2019-11-03 | 2019-10-28 | 2019-10-28 | 0 |
74197 rows × 9 columns
Посчитаем для каждой когорты количество активных пользователей на определённую "неделю жизни".
# группируем данные по когорте и lifetime
cohorts = retention_rate.groupby(['first_activity_week','lifetime']).agg({'user_id':'nunique'}).reset_index()
cohorts
| first_activity_week | lifetime | user_id | |
|---|---|---|---|
| 0 | 2019-10-07 | 0 | 1130 |
| 1 | 2019-10-07 | 1 | 272 |
| 2 | 2019-10-07 | 2 | 170 |
| 3 | 2019-10-07 | 3 | 119 |
| 4 | 2019-10-14 | 0 | 1166 |
| 5 | 2019-10-14 | 1 | 282 |
| 6 | 2019-10-14 | 2 | 155 |
| 7 | 2019-10-21 | 0 | 1094 |
| 8 | 2019-10-21 | 1 | 239 |
| 9 | 2019-10-28 | 0 | 903 |
Чтобы найти Retention Rate, нужно сперва получить число пользователей, изначально бывших в когорте, и на него разделить число пользователей в каждую следующую неделю. Найдём исходное количество пользователей в когорте. Возьмём их число на нулевую неделю:
# считаем количество пользователей, изначально бывших в когорте
initial_users_count = cohorts[cohorts['lifetime'] == 0][['first_activity_week','user_id']]
initial_users_count
#initial_users_count['first_activity_week'] = initial_users_count['first_activity_week'].dt.data
| first_activity_week | user_id | |
|---|---|---|
| 0 | 2019-10-07 | 1130 |
| 4 | 2019-10-14 | 1166 |
| 7 | 2019-10-21 | 1094 |
| 9 | 2019-10-28 | 903 |
# переименновываем столбцы
initial_users_count = initial_users_count.rename(columns={'user_id':'cohort_users'})
# соединяем таблицы
cohorts = cohorts.merge(initial_users_count,on='first_activity_week')
cohorts
| first_activity_week | lifetime | user_id | cohort_users | |
|---|---|---|---|---|
| 0 | 2019-10-07 | 0 | 1130 | 1130 |
| 1 | 2019-10-07 | 1 | 272 | 1130 |
| 2 | 2019-10-07 | 2 | 170 | 1130 |
| 3 | 2019-10-07 | 3 | 119 | 1130 |
| 4 | 2019-10-14 | 0 | 1166 | 1166 |
| 5 | 2019-10-14 | 1 | 282 | 1166 |
| 6 | 2019-10-14 | 2 | 155 | 1166 |
| 7 | 2019-10-21 | 0 | 1094 | 1094 |
| 8 | 2019-10-21 | 1 | 239 | 1094 |
| 9 | 2019-10-28 | 0 | 903 | 903 |
# разделим количество активных пользователей в каждую из недель на исходное число пользователей в когорте
cohorts['retention'] = cohorts['user_id']/cohorts['cohort_users']
cohorts
| first_activity_week | lifetime | user_id | cohort_users | retention | |
|---|---|---|---|---|---|
| 0 | 2019-10-07 | 0 | 1130 | 1130 | 1.000000 |
| 1 | 2019-10-07 | 1 | 272 | 1130 | 0.240708 |
| 2 | 2019-10-07 | 2 | 170 | 1130 | 0.150442 |
| 3 | 2019-10-07 | 3 | 119 | 1130 | 0.105310 |
| 4 | 2019-10-14 | 0 | 1166 | 1166 | 1.000000 |
| 5 | 2019-10-14 | 1 | 282 | 1166 | 0.241852 |
| 6 | 2019-10-14 | 2 | 155 | 1166 | 0.132933 |
| 7 | 2019-10-21 | 0 | 1094 | 1094 | 1.000000 |
| 8 | 2019-10-21 | 1 | 239 | 1094 | 0.218464 |
| 9 | 2019-10-28 | 0 | 903 | 903 | 1.000000 |
# рассчитаем Retention Rate по lifetime
retention = cohorts.pivot_table(index='first_activity_week',columns='lifetime',values='retention',aggfunc='sum')
retention
| lifetime | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| first_activity_week | ||||
| 2019-10-07 | 1.0 | 0.240708 | 0.150442 | 0.10531 |
| 2019-10-14 | 1.0 | 0.241852 | 0.132933 | NaN |
| 2019-10-21 | 1.0 | 0.218464 | NaN | NaN |
| 2019-10-28 | 1.0 | NaN | NaN | NaN |
sns.set(style='white')
plt.figure(figsize=(7, 5))
plt.title('Удержание пользователей')
sns.heatmap(retention, annot=True, fmt='.1%', linewidths=1, linecolor='gray')
plt.xlabel('Лайфтайм')
plt.ylabel('Неделя')
plt.show();
Retention Rate коэффициент удержания низкий 24%, значит 76% отваливаются. С каждой новой когортой падает коэффициент удержания.Посчитаем коэффиент отскока Churn Rate.
Churn Rate — ещё один показатель, который помогает компаниям оценить потенциальную выручку и «здоровье» бизнеса в целом. Он показывает, какой процент пользователей прекращает использовать сервис с течением времени.
# группируем данные по first_activity_week и lifetime
cohorts1 = retention_rate.groupby(['first_activity_week','lifetime']).agg({'user_id':'nunique'}).reset_index()
initial_users_count1 = cohorts1[cohorts1['lifetime'] == 0][['first_activity_week','user_id']]
initial_users_count1 = initial_users_count1.rename(columns={'user_id':'cohort_users'})
cohorts1 = cohorts1.merge(initial_users_count1,on='first_activity_week')
cohorts1['rebound'] = 1-cohorts1['user_id']/cohorts1['cohort_users']
cohorts1
| first_activity_week | lifetime | user_id | cohort_users | rebound | |
|---|---|---|---|---|---|
| 0 | 2019-10-07 | 0 | 1130 | 1130 | 0.000000 |
| 1 | 2019-10-07 | 1 | 272 | 1130 | 0.759292 |
| 2 | 2019-10-07 | 2 | 170 | 1130 | 0.849558 |
| 3 | 2019-10-07 | 3 | 119 | 1130 | 0.894690 |
| 4 | 2019-10-14 | 0 | 1166 | 1166 | 0.000000 |
| 5 | 2019-10-14 | 1 | 282 | 1166 | 0.758148 |
| 6 | 2019-10-14 | 2 | 155 | 1166 | 0.867067 |
| 7 | 2019-10-21 | 0 | 1094 | 1094 | 0.000000 |
| 8 | 2019-10-21 | 1 | 239 | 1094 | 0.781536 |
| 9 | 2019-10-28 | 0 | 903 | 903 | 0.000000 |
rebound = cohorts1.pivot_table(index='first_activity_week',columns='lifetime',values='rebound',aggfunc='sum')
rebound
| lifetime | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| first_activity_week | ||||
| 2019-10-07 | 0.0 | 0.759292 | 0.849558 | 0.89469 |
| 2019-10-14 | 0.0 | 0.758148 | 0.867067 | NaN |
| 2019-10-21 | 0.0 | 0.781536 | NaN | NaN |
| 2019-10-28 | 0.0 | NaN | NaN | NaN |
sns.set(style='white')
plt.figure(figsize=(10, 4))
plt.title('Коэффициент «отскока»')
sns.heatmap(rebound, annot=True, fmt='.1%', linewidths=1, linecolor='gray')
plt.xlabel('Лайфтайм')
plt.ylabel('Неделя')
plt.show();
Churn Rate коэффициент отскока высокий 76%. С каждой новой когортой увеливается коэффициент отскока. Что-то не устраивает в приложении пользователей.
Юнит-экономика и когортный анализ — мощные инструменты для оценки финансового благополучия бизнеса. На этот вопрос отвечает пользовательская активность — количество уникальных активных пользователей за определённое время. Её измеряют тремя показателями:
# выделяем год, номер месяца и номер недели из времени начала сессии
retention_rate['min_year'] = retention_rate['first_date'].dt.year
retention_rate['min_month'] = retention_rate['first_date'].dt.month
retention_rate['min_week'] = retention_rate['first_date'].dt.isocalendar().week
retention_rate['min_date'] = retention_rate['first_date'].dt.date
# cгруппируем данные по дате и неделе, посчитаем количество уникальных пользователей по столбцу user_id и найдём среднее.
dau_total = (
retention_rate.groupby('min_date').agg({'user_id': 'nunique'}).mean()
)
wau_total = (
retention_rate.groupby(['min_year', 'min_week']).agg({'user_id': 'nunique'}).mean()
)
print(f'DAU — количество уникальных пользователей в день - {int(dau_total)}.')
print(f'WAU — количество уникальных пользователей в неделю - {int(wau_total)}.')
DAU — количество уникальных пользователей в день - 153. WAU — количество уникальных пользователей в неделю - 1073.
Количество уникальных пользователй в день DAU - 153. Количество уникальных пользователй в неделю WAU - 1073.
Ещё одна популярная «тщеславная» метрика — sticky factor, или «липкий фактор» в переводе с английского. Sticky factor отражает регулярность использования сервиса или приложения и для недельной аудитории рассчитывается как DAU/WAU. Для месячной аудитории — DAU/MAU.
sticky_wau = dau_total / wau_total * 100
print(f'Для недельной аудитории - {sticky_wau}.')
Для недельной аудитории - user_id 14.285714 dtype: float64.
За неделю количество пользователей, использующие приложение составляет 14%.
# определим начало и конец каждого сеанса каждого пользователя в приложении
# добавляем столбец время в часах
unnecessary_things['event_hour'] = unnecessary_things['time'].dt.hour
# начало сеанса
user_time_min = unnecessary_things.groupby(['user_id', 'data', 'event_hour'], as_index=False)\
.agg({'time':'min'})\
.rename(columns={'time':'event_time_start'})
# конец сеанса
user_time_max = unnecessary_things.groupby(['user_id', 'data', 'event_hour'], as_index=False)\
.agg({'time':'max'})\
.rename(columns={'time':'event_time_stop'})
# объединим столбцы с временем начала и окончания сеанса для пользователей
user_time = pd.merge(user_time_min, user_time_max, on=['user_id', 'data', 'event_hour'])
user_time
| user_id | data | event_hour | event_time_start | event_time_stop | |
|---|---|---|---|---|---|
| 0 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-07 | 13 | 2019-10-07 13:39:45.989359 | 2019-10-07 13:49:41.716617 |
| 1 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-09 | 18 | 2019-10-09 18:33:55.577963 | 2019-10-09 18:42:22.963948 |
| 2 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-21 | 19 | 2019-10-21 19:52:30.778932 | 2019-10-21 19:57:49.029206 |
| 3 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-21 | 20 | 2019-10-21 20:00:00.438922 | 2019-10-21 20:07:30.051028 |
| 4 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-22 | 11 | 2019-10-22 11:18:14.635436 | 2019-10-22 11:30:52.807203 |
| ... | ... | ... | ... | ... | ... |
| 12427 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-02 | 18 | 2019-11-02 18:01:27.094834 | 2019-11-02 18:17:41.386651 |
| 12428 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-02 | 19 | 2019-11-02 19:25:53.794029 | 2019-11-02 19:30:50.471310 |
| 12429 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 | 14 | 2019-11-03 14:32:55.956301 | 2019-11-03 14:48:44.263356 |
| 12430 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 | 15 | 2019-11-03 15:36:01.007440 | 2019-11-03 15:51:57.899997 |
| 12431 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 | 16 | 2019-11-03 16:07:40.932077 | 2019-11-03 16:08:25.388712 |
12432 rows × 5 columns
# посчитаем, cколько длился сеанс и переведем в минуты
user_time['time_spent'] = (user_time['event_time_stop'] - user_time['event_time_start'])\
.astype('timedelta64[s]') / 60
user_time['time_spent'] = round(user_time['time_spent'], 1)
user_time
| user_id | data | event_hour | event_time_start | event_time_stop | time_spent | |
|---|---|---|---|---|---|---|
| 0 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-07 | 13 | 2019-10-07 13:39:45.989359 | 2019-10-07 13:49:41.716617 | 9.9 |
| 1 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-09 | 18 | 2019-10-09 18:33:55.577963 | 2019-10-09 18:42:22.963948 | 8.4 |
| 2 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-21 | 19 | 2019-10-21 19:52:30.778932 | 2019-10-21 19:57:49.029206 | 5.3 |
| 3 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-21 | 20 | 2019-10-21 20:00:00.438922 | 2019-10-21 20:07:30.051028 | 7.5 |
| 4 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-22 | 11 | 2019-10-22 11:18:14.635436 | 2019-10-22 11:30:52.807203 | 12.6 |
| ... | ... | ... | ... | ... | ... | ... |
| 12427 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-02 | 18 | 2019-11-02 18:01:27.094834 | 2019-11-02 18:17:41.386651 | 16.2 |
| 12428 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-02 | 19 | 2019-11-02 19:25:53.794029 | 2019-11-02 19:30:50.471310 | 4.9 |
| 12429 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 | 14 | 2019-11-03 14:32:55.956301 | 2019-11-03 14:48:44.263356 | 15.8 |
| 12430 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 | 15 | 2019-11-03 15:36:01.007440 | 2019-11-03 15:51:57.899997 | 15.9 |
| 12431 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 | 16 | 2019-11-03 16:07:40.932077 | 2019-11-03 16:08:25.388712 | 0.7 |
12432 rows × 6 columns
# добавим в нее время продолжительности сеанса для каждого пользователя
taum = user_time.groupby('user_id', as_index=False)\
.agg({'time_spent':'sum', 'data':'count'})\
.rename(columns={'data':'visits'})
taum.head()
| user_id | time_spent | visits | |
|---|---|---|---|
| 0 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 43.7 | 5 |
| 1 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 177.5 | 10 |
| 2 | 00463033-5717-4bf1-91b4-09183923b9df | 21.2 | 2 |
| 3 | 004690c3-5a84-4bb7-a8af-e0c8f8fca64e | 87.5 | 7 |
| 4 | 00551e79-152e-4441-9cf7-565d7eb04090 | 9.3 | 3 |
# общее время сеанса
taum['time_spent'].sum()
123671.30000000002
# среднее время сеанса
taum['time_spent'].sum() / taum['user_id'].nunique()
28.80766363848125
Среднее время продолжительности сеанса 29 минут, округляем - 30 минут. Это время будем использвать для расчeтa тайм-аута.
Вывод исследовательский анализ данных: Всего в данных 74197 событий и 4293 пользователя. В среднем на пользователя приходится 17 событий. Медианное количество при этом составляет 9. Минимальное значение 1, максимальное количество действий 478.
В данных 3 источника:yandex(34286), google(20445), other(19466) , с которых пользователь установил приложение. В течении 28 дней пользователи с разных источников заходили в приложениене. У всех источников были всплески и падения по дням. Больше всего событий проходило из источника yandex(34286). В 1,7 раза меньше google(20445) и other(19466). Заметны снижения активности пользователй в выходные дни.
В представленных данных 9 действий: tips_show(40055), photos_show(10012), search(6784), advert_open(6164), contacts_show(4529), map(3881), favorites_add(1417), tips_click(814), contacts_call(541), который может выполнить пользователь. В каждом действии есть всплески по времени.Особенно сильно заметны всплески у действий: tips_show, tips_click. Больше всего событий у tips_show(40055)54% от общего количества действий, в 4 меньше photos_show(10012)13% и остальные действия составляют 24130 - 33%. Пользователи пользуются приложением в буднии дни. В первых действиях нет contact_call. Количество действий снижается от tips_show до tips_clik. Особенно резкое снижение идет от tips_show(1368) до contacts_show(173). Последнии действия все 9. Количество действий также снижается от tips_show(2403)57% от общего количества пользователей до tips_clik(26)0,6%, но не так резко как первых действиях. Порядок последних действий пользователей меняется по сравнению с первыми действиями.
Через поисковую систему(6394) до просмотра контактов доходит 1311(21%) пользователей. Перед ЦС пользователи просматривают фотографии и открывают карточку объявления.
Через рекомендованное объявление (662) до просмотра контактов доходит 547(83%) пользователей. Пользователю не приходится гулять по приложению, а сразу они просматривают контакты. Если бы все пользователи пользовались рекомендованным объявление, то до Целевого действия доходило бы 83% людей. При поиске по сайту доходят 21% пользователей , т.к. путь пользователя удлиняется и на каждом этапе происходит отток людей.
Минимальная дата: 2019-10-07 00:00:00.431357. Максимальная дата: 2019-11-03 23:58:12.532487. Период исследования составляет практически 28 дней без 2 минут.
Retention Rate коэффициент удержания низкий 24%, значит 76% отваливаются. С каждой новой когортой падает коэффициент удержания.
Churn Rate коэффициент отскока высокий 76%. С каждой новой когортой увеливается коэффициент отскока. Что-то не устраивает в приложении пользователей.
Количество уникальных пользователй в день DAU - 153. Количество уникальных пользователй в неделю WAU - 1073. Среднее время продолжительности сеанса 29 минут, округляем - 30 минут. Это время будем использвать для расчете тайм-аута.
Выделяем сессии относительно тайм-аута.
Тайм-аут визита — это время бездействия посетителя на сайте, после которого визит считается завершенным. По умолчанию тайм-аут визита составляет 30 минут. Поэтому если в отчетах счетчика присутствует большое количество внутренних переходов, и при этом на сайте есть страницы, на которых пользователи могли бы остаться более чем нам 30 минут, то необходимо увеличить время тайм-аута визита. Максимальное значение тайм-аута составляет 360 минут. Однако помните, что без надобности менять тайм-аут визита не требуется, иначе есть риск получить искаженную статистику. https://yandex.ru/blog/metrika-club/gid-po-metrike-nastroyki-schetchika
# преобразовываем дату и время
unnecessary_things['time'] = pd.to_datetime(unnecessary_things['time'])
# определяем разницу за 30 минут для каждой группы с суммарной суммой
unnecessary_things1 = unnecessary_things.sort_values(by=['user_id', 'time'])
g = (unnecessary_things1.groupby('user_id')['time'].diff() > pd.Timedelta('30Min')).cumsum()
#создаем счетчик групп
unnecessary_things1['session_id'] = unnecessary_things1.groupby(['user_id', g], sort=False).ngroup() + 1
unnecessary_things1#.head(100)
| user_id | source | time | event | data | event_hour | session_id | |
|---|---|---|---|---|---|---|---|
| 2171 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:39:45.989359 | tips_show | 2019-10-07 | 13 | 1 |
| 2172 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:40:31.052909 | tips_show | 2019-10-07 | 13 | 1 |
| 2173 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:41:05.722489 | tips_show | 2019-10-07 | 13 | 1 |
| 2174 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:43:20.735461 | tips_show | 2019-10-07 | 13 | 1 |
| 2175 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:45:30.917502 | tips_show | 2019-10-07 | 13 | 1 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 19048 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 15:51:23.959572 | tips_show | 2019-11-03 | 15 | 10368 | |
| 19049 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 15:51:57.899997 | contacts_show | 2019-11-03 | 15 | 10368 | |
| 19050 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 16:07:40.932077 | tips_show | 2019-11-03 | 16 | 10368 | |
| 19051 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 16:08:18.202734 | tips_show | 2019-11-03 | 16 | 10368 | |
| 19052 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 16:08:25.388712 | tips_show | 2019-11-03 | 16 | 10368 |
74197 rows × 7 columns
# проверяем на дубликаты
unnecessary_things1[unnecessary_things1.duplicated(subset=['session_id','event'])]
| user_id | source | time | event | data | event_hour | session_id | |
|---|---|---|---|---|---|---|---|
| 2172 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:40:31.052909 | tips_show | 2019-10-07 | 13 | 1 |
| 2173 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:41:05.722489 | tips_show | 2019-10-07 | 13 | 1 |
| 2174 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:43:20.735461 | tips_show | 2019-10-07 | 13 | 1 |
| 2175 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:45:30.917502 | tips_show | 2019-10-07 | 13 | 1 |
| 2176 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:45:43.212340 | tips_show | 2019-10-07 | 13 | 1 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 19048 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 15:51:23.959572 | tips_show | 2019-11-03 | 15 | 10368 | |
| 19049 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 15:51:57.899997 | contacts_show | 2019-11-03 | 15 | 10368 | |
| 19050 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 16:07:40.932077 | tips_show | 2019-11-03 | 16 | 10368 | |
| 19051 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 16:08:18.202734 | tips_show | 2019-11-03 | 16 | 10368 | |
| 19052 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 16:08:25.388712 | tips_show | 2019-11-03 | 16 | 10368 |
56343 rows × 7 columns
# удаляем дубликаты
unnecessary_things1=unnecessary_things1.drop_duplicates(subset=['session_id','event'])
unnecessary_things1
| user_id | source | time | event | data | event_hour | session_id | |
|---|---|---|---|---|---|---|---|
| 2171 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-07 13:39:45.989359 | tips_show | 2019-10-07 | 13 | 1 |
| 2180 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-09 18:33:55.577963 | map | 2019-10-09 | 18 | 2 |
| 2182 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-09 18:40:28.738785 | tips_show | 2019-10-09 | 18 | 2 |
| 2184 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-21 19:52:30.778932 | tips_show | 2019-10-21 | 19 | 3 |
| 2186 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | other | 2019-10-21 19:53:38.767230 | map | 2019-10-21 | 19 | 3 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 19021 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-02 19:26:07.834494 | contacts_show | 2019-11-02 | 19 | 10366 | |
| 19024 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 14:32:55.956301 | tips_show | 2019-11-03 | 14 | 10367 | |
| 19025 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 14:33:47.921863 | contacts_show | 2019-11-03 | 14 | 10367 | |
| 19039 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 15:36:01.007440 | tips_show | 2019-11-03 | 15 | 10368 | |
| 19044 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-11-03 15:48:05.420247 | contacts_show | 2019-11-03 | 15 | 10368 |
17854 rows × 7 columns
unnecessary_things['event'].value_counts()
tips_show 40055 photos_show 10012 search 6784 advert_open 6164 contacts_show 4529 map 3881 favorites_add 1417 tips_click 814 contacts_call 541 Name: event, dtype: int64
# количество сессий
unnecessary_things1['session_id'].nunique()
10368
# количество пользователей
unnecessary_things1['user_id'].nunique()
4293
# количество сессий на пользователя
unnecessary_things1['session_id'].nunique()/unnecessary_things1['user_id'].nunique()
2.4150943396226414
# количество событий
len(unnecessary_things1)
17854
# количество сессий на событие
len(unnecessary_things1)/unnecessary_things1['session_id'].nunique()
1.7220293209876543
Всего получилось 10368 сессий.На каждого пользователя приходится 2 сессии. На каждую сессию приходится по 2 события.
def add_features(df):
"""Функция генерации новых столбцов для исходной таблицы
Args:
df (pd.DataFrame): исходная таблица.
Returns:
pd.DataFrame: таблица с новыми признаками.
"""
# сортируем по id и времени
sorted_df = df.sort_values(by=['session_id', 'time']).copy()
# добавляем шаги событий
sorted_df['step'] = sorted_df.groupby('session_id').cumcount() + 1
# добавляем узлы-источники и целевые узлы
# узлы-источники - это сами события
sorted_df['source'] = sorted_df['event']
# добавляем целевые узлы
sorted_df['target'] = sorted_df.groupby('session_id')['source'].shift(-1)
# возврат таблицы без имени событий
return sorted_df.drop(['event'], axis=1)
# преобразуем таблицу
table = add_features(unnecessary_things1)
table.head(10)
| user_id | source | time | data | event_hour | session_id | step | target | |
|---|---|---|---|---|---|---|---|---|
| 2171 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | tips_show | 2019-10-07 13:39:45.989359 | 2019-10-07 | 13 | 1 | 1 | NaN |
| 2180 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | map | 2019-10-09 18:33:55.577963 | 2019-10-09 | 18 | 2 | 1 | tips_show |
| 2182 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | tips_show | 2019-10-09 18:40:28.738785 | 2019-10-09 | 18 | 2 | 2 | NaN |
| 2184 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | tips_show | 2019-10-21 19:52:30.778932 | 2019-10-21 | 19 | 3 | 1 | map |
| 2186 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | map | 2019-10-21 19:53:38.767230 | 2019-10-21 | 19 | 3 | 2 | NaN |
| 2198 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | map | 2019-10-22 11:18:14.635436 | 2019-10-22 | 11 | 4 | 1 | tips_show |
| 2199 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | tips_show | 2019-10-22 11:19:10.529462 | 2019-10-22 | 11 | 4 | 2 | NaN |
| 42960 | 00157779-810c-4498-9e05-a1e9e3cedf93 | search | 2019-10-19 21:34:33.849769 | 2019-10-19 | 21 | 5 | 1 | photos_show |
| 42963 | 00157779-810c-4498-9e05-a1e9e3cedf93 | photos_show | 2019-10-19 21:40:38.990477 | 2019-10-19 | 21 | 5 | 2 | NaN |
| 42969 | 00157779-810c-4498-9e05-a1e9e3cedf93 | search | 2019-10-20 18:49:24.115634 | 2019-10-20 | 18 | 6 | 1 | photos_show |
table['step'].nunique()
6
table['source'].value_counts()
tips_show 6035 search 2974 photos_show 2526 map 2133 contacts_show 1703 advert_open 1254 favorites_add 501 tips_click 416 contacts_call 312 Name: source, dtype: int64
table.groupby('session_id')['step'].count().describe()
count 10368.000000 mean 1.722029 std 0.893418 min 1.000000 25% 1.000000 50% 1.000000 75% 2.000000 max 6.000000 Name: step, dtype: float64
# считаем количество сессий по категориям
count_session_id = table.groupby("session_id").agg({"source": lambda x: tuple(x)}).reset_index()#.head(100)
count_session_id5 = count_session_id.groupby('source').agg({'session_id': 'nunique'}).reset_index().sort_values(by = 'session_id', ascending=False)
count_session_id5.head()
| source | session_id | |
|---|---|---|
| 295 | (tips_show,) | 2677 |
| 171 | (photos_show,) | 1320 |
| 202 | (search,) | 762 |
| 148 | (map, tips_show) | 547 |
| 247 | (search, photos_show) | 524 |
# смотрим сколько действий по шагам
dff1 = table.groupby('step').agg({'source': ['unique', 'count']})#.reset_index()#.sort_values(by = 'count', ascending=False).head(60)
# преобразовываем MultiIndex в Index
dff1.columns = dff1.columns.map(''.join)
dff1.rename(columns={'sourceunique': 'event', 'sourcecount': 'count'}, inplace = True)
dff1.reset_index()#.sort_values(by = 'count', ascending=False).head(60)
| step | event | count | |
|---|---|---|---|
| 0 | 1 | [tips_show, map, search, photos_show, contacts... | 10368 |
| 1 | 2 | [tips_show, map, photos_show, advert_open, con... | 5076 |
| 2 | 3 | [favorites_add, contacts_show, map, contacts_c... | 1818 |
| 3 | 4 | [contacts_show, contacts_call, advert_open, ti... | 503 |
| 4 | 5 | [contacts_call, favorites_add, map, advert_ope... | 83 |
| 5 | 6 | [advert_open, search, contacts_call] | 6 |
dff = table.groupby('session_id').agg({'source': ['unique', 'count']})
# преобразовываем MultiIndex в Index
dff.columns = dff.columns.map(''.join)
dff.rename(columns={'sourceunique': 'event', 'sourcecount': 'count'}, inplace = True)
dff.reset_index().head()
| session_id | event | count | |
|---|---|---|---|
| 0 | 1 | [tips_show] | 1 |
| 1 | 2 | [map, tips_show] | 2 |
| 2 | 3 | [tips_show, map] | 2 |
| 3 | 4 | [map, tips_show] | 2 |
| 4 | 5 | [search, photos_show] | 2 |
# ограничемся з шагами, при среднем значении 2
df_comp = table[table['step'] <= 3].copy().reset_index(drop=True)
PATH_TO_CSV = 'https://raw.githubusercontent.com/rusantsovsv/senkey_tutorial/main/csv/senkey_data_tutorial.csv'
def get_source_index(df):
"""Функция генерации индексов source
Args:
df (pd.DataFrame): исходная таблица с признаками step, source, target.
Returns:
dict: словарь с индексами, именами и соответсвиями индексов именам source.
"""
res_dict = {}
count = 0
# получаем индексы источников
for no, step in enumerate(df['step'].unique().tolist()):
# получаем уникальные наименования для шага
res_dict[no+1] = {}
res_dict[no+1]['sources'] = df[df['step'] == step]['source'].unique().tolist()
res_dict[no+1]['sources_index'] = []
for i in range(len(res_dict[no+1]['sources'])):
res_dict[no+1]['sources_index'].append(count)
count += 1
# соединим списки
for key in res_dict:
res_dict[key]['sources_dict'] = {}
for name, no in zip(res_dict[key]['sources'], res_dict[key]['sources_index']):
res_dict[key]['sources_dict'][name] = no
return res_dict
# создаем словарь
source_indexes = get_source_index(df_comp)
def generate_random_color():
"""Случайная генерация цветов rgba
Args:
Returns:
str: Строка со сгенерированными параметрами цвета
"""
# сгенерим значение для каждого канала
r, g, b = np.random.randint(255, size=3)
return f'rgba({r}, {g}, {b}, 1)'
def colors_for_sources(mode):
"""Генерация цветов rgba
Args:
mode (str): сгенерировать случайные цвета, если 'random', а если 'custom' -
использовать заранее подготовленные
Returns:
dict: словарь с цветами, соответствующими каждому индексу
"""
# словарь, в который сложим цвета в соответствии с индексом
colors_dict = {}
if mode == 'random':
# генерим случайные цвета
for label in df_comp['source'].unique():
r, g, b = np.random.randint(255, size=3)
colors_dict[label] = f'rgba({r}, {g}, {b}, 1)'
elif mode == 'custom':
# присваиваем ранее подготовленные цвета
colors = requests.get('https://raw.githubusercontent.com/rusantsovsv/senkey_tutorial/main/json/colors_senkey.json').json()
for no, label in enumerate(df_comp['source'].unique()):
colors_dict[label] = colors['custom_colors'][no]
return colors_dict
colors_dict = colors_for_sources(mode='custom')
def percent_users(sources, targets, values):
"""
Расчет уникальных id в процентах (для вывода в hover text каждого узла)
Args:
sources (list): список с индексами source.
targets (list): список с индексами target.
values (list): список с "объемами" потоков.
Returns:
list: список с "объемами" потоков в процентах
"""
# объединим источники и метки и найдем пары
zip_lists = list(zip(sources, targets, values))
new_list = []
# подготовим список словарь с общим объемом трафика в узлах
unique_dict = {}
# проходим по каждому узлу
for source, target, value in zip_lists:
if source not in unique_dict:
# находим все источники и считаем общий трафик
unique_dict[source] = 0
for sr, tg, vl in zip_lists:
if sr == source:
unique_dict[source] += vl
# считаем проценты
for source, target, value in zip_lists:
new_list.append(round(100 * value / unique_dict[source], 1))
return new_list
# Создание словаря с данными для отрисовки диаграммы
def lists_for_plot(source_indexes=source_indexes, colors=colors_dict, frac=10):
"""
Создаем необходимые для отрисовки диаграммы переменные списков и возвращаем
их в виде словаря
Args:
source_indexes (dict): словарь с именами и индексами source.
colors (dict): словарь с цветами source.
frac (int): ограничение на минимальный "объем" между узлами.
Returns:
dict: словарь со списками, необходимыми для диаграммы.
"""
sources = []
targets = []
values = []
labels = []
link_color = []
link_text = []
# проходим по каждому шагу
for step in tqdm(sorted(df_comp['step'].unique()), desc='Шаг'):
if step + 1 not in source_indexes:
continue
# получаем индекс источника
temp_dict_source = source_indexes[step]['sources_dict']
# получаем индексы цели
temp_dict_target = source_indexes[step+1]['sources_dict']
# проходим по каждой возможной паре, считаем количество таких пар
for source, index_source in tqdm(temp_dict_source.items()):
for target, index_target in temp_dict_target.items():
# делаем срез данных и считаем количество id
temp_df = df_comp[(df_comp['step'] == step)&(df_comp['source'] == source)&(df_comp['target'] == target)]
value = len(temp_df)
# проверяем минимальный объем потока и добавляем нужные данные
if value > frac:
sources.append(index_source)
targets.append(index_target)
values.append(value)
# делаем поток прозрачным для лучшего отображения
link_color.append(colors[source].replace(', 1)', ', 0.2)'))
labels = []
colors_labels = []
for key in source_indexes:
for name in source_indexes[key]['sources']:
labels.append(name)
colors_labels.append(colors[name])
# посчитаем проценты всех потоков
perc_values = percent_users(sources, targets, values)
# добавим значения процентов для howertext
link_text = []
for perc in perc_values:
link_text.append(f"{perc}%")
# возвратим словарь с вложенными списками
return {'sources': sources,
'targets': targets,
'values': values,
'labels': labels,
'colors_labels': colors_labels,
'link_color': link_color,
'link_text': link_text}
# создаем словарь
data_for_plot = lists_for_plot()
Шаг: 0%| | 0/3 [00:00<?, ?it/s]
0%| | 0/8 [00:00<?, ?it/s]
0%| | 0/9 [00:00<?, ?it/s]
def plot_senkey_diagram(data_dict=data_for_plot):
"""
Функция для генерации объекта диаграммы Сенкей
Args:
data_dict (dict): словарь со списками данных для построения.
Returns:
plotly.graph_objs._figure.Figure: объект изображения.
"""
fig = go.Figure(data=[go.Sankey(
domain = dict(
x = [0,1],
y = [0,1]
),
orientation = "h",
valueformat = ".0f",
node = dict(
pad = 50,
thickness = 15,
line = dict(color = "black", width = 0.1),
label = data_dict['labels'],
color = data_dict['colors_labels']
),
link = dict(
source = data_dict['sources'],
target = data_dict['targets'],
value = data_dict['values'],
label = data_dict['link_text'],
color = data_dict['link_color']
))])
fig.update_layout(title_text="Sankey Diagram", font_size=10, width=1100, height=700)
# возвращаем объект диаграммы
return fig
# сохраняем диаграмму в переменную
senkey_diagram = plot_senkey_diagram()
senkey_diagram.show()
Построим воронку по пользователям, которые пришли в просмотр контактов через открытие карты объявления
# выбираем уникальных пользователей в датасете по map(открыл карту объявления)
map_event = unnecessary_things.query('event == "map"')
map_uniq = map_event['user_id'].unique().tolist()
# выбираем пользователей, которые карту объявления
map_user = unnecessary_things.query('user_id == @ map_uniq')
# выбираем события , по которым будем строить воронку
map_tips_show_contacts_show = map_user.query('event == "map" or event == "tips_show" or event == "contacts_show"')
# собираем количество уникальных пользователей по этапам
funnel_map = map_tips_show_contacts_show.groupby('event').agg({'user_id': 'nunique'})\
.sort_values(by = 'user_id', ascending=False).reset_index()
# считаем конверсию по уникальным пользователям
funnel_map['conversion'] = round(funnel_map['user_id']/ funnel_map['user_id'].sum()*100,2)
# считаем какая доля пользователей дошла до ЦС
funnel_map['funnel'] = round(funnel_map['user_id'] / funnel_map['user_id'].shift(1)*100,2)
funnel_map
| event | user_id | conversion | funnel | |
|---|---|---|---|---|
| 0 | map | 1456 | 47.01 | NaN |
| 1 | tips_show | 1352 | 43.66 | 92.86 |
| 2 | contacts_show | 289 | 9.33 | 21.38 |
# строим воронку
fig = go.Figure()
fig.add_trace(go.Funnel(
x = funnel_map['user_id'],
y = funnel_map['event'],
opacity = 0.6,
textposition = "auto",
textinfo = "value+percent previous"
))
fig.update_layout(title='Воронка по пользователям, которые пришли через открытие карты объявления', title_x = 0.5)
fig.show()
После открытия карты объявления, увидели рекомендованное объявление 93% пользователей. И 21 % пользователей доходят до целевого действия. Конверсия в Цeлевое действие map(1456) - contacts_show(289) - 19.85%.
Построим воронку по пользователям, которые пришли в просмотр контактов через открытие карточки объявления
# выбираем уникальных пользователей в датасете по advert_open(открыл карточку объявления)
advert_open_event = unnecessary_things.query('event == "advert_open"')
advert_open_uniq = advert_open_event['user_id'].unique().tolist()
# выбираем пользователей, которые карту объявления
advert_open_user = unnecessary_things.query('user_id == @ advert_open_uniq')
# выбираем события , по которым будем строить воронку
advert_open_tips_show_contacts_show = advert_open_user\
.query('event == "tips_show" or event == "advert_open" or event == "contacts_show"')
# собираем количество уникальных пользователей по этапам
funnel_advert_open = advert_open_tips_show_contacts_show.groupby('event')\
.agg({'user_id': 'nunique'}).sort_values(by = 'user_id', ascending=False).reset_index()
# считаем конверсию по уникальным пользователям
funnel_advert_open['conversion'] = round(funnel_advert_open['user_id']/ funnel_advert_open['user_id'].sum()*100,2)
# считаем какая доля пользователей дошла до ЦС
funnel_advert_open['funnel'] = round(funnel_advert_open['user_id'] / funnel_advert_open['user_id'].shift(1)*100,2)
funnel_advert_open
| event | user_id | conversion | funnel | |
|---|---|---|---|---|
| 0 | advert_open | 751 | 50.78 | NaN |
| 1 | tips_show | 590 | 39.89 | 78.56 |
| 2 | contacts_show | 138 | 9.33 | 23.39 |
# строим воронку
fig = go.Figure()
fig.add_trace(go.Funnel(
x = funnel_advert_open['user_id'],
y = funnel_advert_open['event'],
opacity = 0.6,
textposition = "auto",
textinfo = "value+percent previous"
))
fig.update_layout(title='Воронка по пользователям, которые пришли через открытие карточки объявления', title_x = 0.5)
fig.show()
После открытия карточки объявления, увидели рекомендованные объявления 79% пользователей. И 23 % пользователей доходят до целевого действия. Конверсия в Цeлевое действие advert_open(751) - contacts_show(138) составляет 18.38%.
Построим воронку по пользователям, которые пришли в просмотр контактов через поиск по приложению
# выбираем уникальных пользователей в датасете по search(поиск по сайту)
search_event = unnecessary_things.query('event == "search"')
search_uniq = search_event['user_id'].unique().tolist()
# выбираем пользователей, которые карту объявления
search_user = unnecessary_things.query('user_id == @ search_uniq')
# выбираем события , по которым будем строить воронку
search_tips_show_contacts_show = search_user.query('event == "search" or event == "tips_show"or event == "contacts_show"')
# собираем количество уникальных пользователей по этапам
funnel_search = search_tips_show_contacts_show.groupby('event')\
.agg({'user_id': 'nunique'})\
.sort_values(by = 'user_id', ascending=False).reset_index()
# считаем конверсию по уникальным пользователям
funnel_search['conversion'] = round(funnel_search['user_id']/ funnel_search['user_id'].sum()*100,2)
# считаем какая доля пользователей дошла до ЦС
funnel_search['funnel'] = round(funnel_search['user_id'] / funnel_search['user_id'].shift(1)*100,2)
funnel_search
| event | user_id | conversion | funnel | |
|---|---|---|---|---|
| 0 | search | 1666 | 58.58 | NaN |
| 1 | tips_show | 801 | 28.16 | 48.08 |
| 2 | contacts_show | 377 | 13.26 | 47.07 |
# строим воронку
fig = go.Figure()
fig.add_trace(go.Funnel(
x = funnel_search['user_id'],
y = funnel_search['event'],
opacity = 0.6,
textposition = "auto",
textinfo = "value+percent previous"
))
fig.update_layout(title='Воронка по пользователям, которые пришли через поиск по приложению', title_x = 0.5)
fig.show()
После поиска по сайту, увидели рекомендованные объявления 48% пользователей. И 47 % пользователей доходят до целевого действия. Конверсия в Цeлевое действие search(1666) - contacts_show(377) составляет 22.63%.
Рассмотрели 3 сценария:
1.search(100%)-tips_show(48%)-contacts_show(47%).
2.advert_open(100%) - tips_show(79%)-contacts_show(23%)
3.map(100%) - tips_show(93%)-contacts_show(21%).
Выше всего конверсия в Цeлевое действие search(1666) - contacts_show(377) составляет 22.63%.
На втором месте конверсия в Цeлевое действие map(1456) - contacts_show(289) - 19.85%.
На третьем месте конверсия в Цeлевое действие advert_open(751) - contacts_show(138) составляет 18.38%.
# создаем таблицу с первыми данными по contacts_show
session_id_contacts_show = table.sort_values(by=['session_id', 'time'])
session_id_contacts_show = table.query('source == "contacts_show"').groupby('session_id')['time'].min().reset_index()
session_id_contacts_show.columns = ['session_id', 'time_first_contacts_show']
session_id_contacts_show.head()
| session_id | time_first_contacts_show | |
|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 |
| 1 | 8 | 2019-10-29 21:26:40.258472 |
| 2 | 9 | 2019-10-30 08:01:05.420773 |
| 3 | 10 | 2019-11-03 17:12:09.708771 |
| 4 | 18 | 2019-10-25 16:44:41.263364 |
# создаем данные по первому действию
session_id_event_first_time_min = table.sort_values(by=['session_id', 'time'])
# разделяем в таблице данные на начало и конец
session_id_event_first_time_min =session_id_event_first_time_min.groupby('session_id').agg({'time': ['min'],'source': ['first']})
# преобразовываем MultiIndex в Index
session_id_event_first_time_min.columns = session_id_event_first_time_min.columns.map(''.join)
# меняем название столбцов
session_id_event_first_time_min.rename(columns={'timemin': 'time_min', 'sourcefirst': 'event_first'}, inplace = True)
session_id_event_first_time_min.reset_index().head()
| session_id | time_min | event_first | |
|---|---|---|---|
| 0 | 1 | 2019-10-07 13:39:45.989359 | tips_show |
| 1 | 2 | 2019-10-09 18:33:55.577963 | map |
| 2 | 3 | 2019-10-21 19:52:30.778932 | tips_show |
| 3 | 4 | 2019-10-22 11:18:14.635436 | map |
| 4 | 5 | 2019-10-19 21:34:33.849769 | search |
# соединяем таблицы с первыми данными по contacts_show и данные по первому действию
session_id_contacts_show_first = session_id_contacts_show.merge(session_id_event_first_time_min, on='session_id', how='left')
session_id_contacts_show_first.head()
| session_id | time_first_contacts_show | time_min | event_first | |
|---|---|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 | 2019-10-20 18:49:24.115634 | search |
| 1 | 8 | 2019-10-29 21:26:40.258472 | 2019-10-29 21:18:24.850073 | search |
| 2 | 9 | 2019-10-30 08:01:05.420773 | 2019-10-30 07:50:45.948358 | search |
| 3 | 10 | 2019-11-03 17:12:09.708771 | 2019-11-03 17:12:09.708771 | contacts_show |
| 4 | 18 | 2019-10-25 16:44:41.263364 | 2019-10-25 16:44:41.263364 | contacts_show |
# считаем количество дней
session_id_contacts_show_first['delta_time'] = session_id_contacts_show_first['time_first_contacts_show'] - session_id_contacts_show_first['time_min']
session_id_contacts_show_first.sort_values(by='delta_time').head()
| session_id | time_first_contacts_show | time_min | event_first | delta_time | |
|---|---|---|---|---|---|
| 1568 | 9616 | 2019-10-27 12:34:17.079917 | 2019-10-27 12:34:17.079917 | contacts_show | 0 days |
| 591 | 3786 | 2019-10-09 13:38:51.658132 | 2019-10-09 13:38:51.658132 | contacts_show | 0 days |
| 1156 | 7168 | 2019-10-13 04:22:31.084013 | 2019-10-13 04:22:31.084013 | contacts_show | 0 days |
| 594 | 3818 | 2019-10-14 19:14:18.179125 | 2019-10-14 19:14:18.179125 | contacts_show | 0 days |
| 1459 | 9088 | 2019-10-10 13:31:12.999336 | 2019-10-10 13:31:12.999336 | contacts_show | 0 days |
# считаем количество пустых значений(contacts_show)
session_id_contacts_show_first.query('delta_time == "0 days 00:00:00"').count()
session_id 543 time_first_contacts_show 543 time_min 543 event_first 543 delta_time 543 dtype: int64
# удалаем пустые значения
session_id_contacts_show_first = session_id_contacts_show_first.query('delta_time != "0 days 00:00:00"')
session_id_contacts_show_first.head()
| session_id | time_first_contacts_show | time_min | event_first | delta_time | |
|---|---|---|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 | 2019-10-20 18:49:24.115634 | search | 0 days 00:27:54.544165 |
| 1 | 8 | 2019-10-29 21:26:40.258472 | 2019-10-29 21:18:24.850073 | search | 0 days 00:08:15.408399 |
| 2 | 9 | 2019-10-30 08:01:05.420773 | 2019-10-30 07:50:45.948358 | search | 0 days 00:10:19.472415 |
| 5 | 19 | 2019-10-28 13:10:40.331441 | 2019-10-28 13:08:15.809056 | search | 0 days 00:02:24.522385 |
| 8 | 30 | 2019-10-22 13:08:09.140381 | 2019-10-22 13:02:26.636223 | map | 0 days 00:05:42.504158 |
session_id_contacts_show_first.describe()
| session_id | delta_time | |
|---|---|---|
| count | 1160.000000 | 1160 |
| mean | 5403.843966 | 0 days 00:09:45.569282692 |
| std | 2969.959565 | 0 days 00:13:38.461736017 |
| min | 6.000000 | 0 days 00:00:00.158918 |
| 25% | 2936.750000 | 0 days 00:01:23.731704750 |
| 50% | 5564.500000 | 0 days 00:04:51.950202500 |
| 75% | 7882.250000 | 0 days 00:12:20.300075250 |
| max | 10368.000000 | 0 days 03:21:08.091077 |
Среднее время от первого действия до Целового составляет 10 минут.
# переведем дни в минуты
session_id_contacts_show_first['minutes'] = session_id_contacts_show_first['delta_time']/np.timedelta64(1,'m')
session_id_contacts_show_first
| session_id | time_first_contacts_show | time_min | event_first | delta_time | minutes | |
|---|---|---|---|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 | 2019-10-20 18:49:24.115634 | search | 0 days 00:27:54.544165 | 27.909069 |
| 1 | 8 | 2019-10-29 21:26:40.258472 | 2019-10-29 21:18:24.850073 | search | 0 days 00:08:15.408399 | 8.256807 |
| 2 | 9 | 2019-10-30 08:01:05.420773 | 2019-10-30 07:50:45.948358 | search | 0 days 00:10:19.472415 | 10.324540 |
| 5 | 19 | 2019-10-28 13:10:40.331441 | 2019-10-28 13:08:15.809056 | search | 0 days 00:02:24.522385 | 2.408706 |
| 8 | 30 | 2019-10-22 13:08:09.140381 | 2019-10-22 13:02:26.636223 | map | 0 days 00:05:42.504158 | 5.708403 |
| ... | ... | ... | ... | ... | ... | ... |
| 1697 | 10360 | 2019-10-29 16:13:00.681459 | 2019-10-29 16:12:15.417343 | tips_show | 0 days 00:00:45.264116 | 0.754402 |
| 1699 | 10365 | 2019-11-02 18:17:41.386651 | 2019-11-02 18:01:27.094834 | tips_show | 0 days 00:16:14.291817 | 16.238197 |
| 1700 | 10366 | 2019-11-02 19:26:07.834494 | 2019-11-02 19:25:53.794029 | tips_show | 0 days 00:00:14.040465 | 0.234008 |
| 1701 | 10367 | 2019-11-03 14:33:47.921863 | 2019-11-03 14:32:55.956301 | tips_show | 0 days 00:00:51.965562 | 0.866093 |
| 1702 | 10368 | 2019-11-03 15:48:05.420247 | 2019-11-03 15:36:01.007440 | tips_show | 0 days 00:12:04.412807 | 12.073547 |
1160 rows × 6 columns
session_id_contacts_show_first.describe()
| session_id | delta_time | minutes | |
|---|---|---|---|
| count | 1160.000000 | 1160 | 1160.000000 |
| mean | 5403.843966 | 0 days 00:09:45.569282692 | 9.759488 |
| std | 2969.959565 | 0 days 00:13:38.461736017 | 13.641029 |
| min | 6.000000 | 0 days 00:00:00.158918 | 0.002649 |
| 25% | 2936.750000 | 0 days 00:01:23.731704750 | 1.395528 |
| 50% | 5564.500000 | 0 days 00:04:51.950202500 | 4.865837 |
| 75% | 7882.250000 | 0 days 00:12:20.300075250 | 12.338335 |
| max | 10368.000000 | 0 days 03:21:08.091077 | 201.134851 |
# среднее время по действиям
session_id_contacts_show_first.groupby('event_first').agg({'minutes': 'mean'})
| minutes | |
|---|---|
| event_first | |
| advert_open | 4.794377 |
| favorites_add | 6.352063 |
| map | 10.617769 |
| photos_show | 10.121990 |
| search | 8.656574 |
| tips_click | 26.086436 |
| tips_show | 10.388056 |
# строим распределение действий по среднему времени
session_id_contacts_show_first.groupby('event_first').agg({'minutes': 'mean'}).sort_values(by='minutes', ascending=False).plot(figsize=(15, 5), grid=True)
plt.title('Распределение от первого действия до Целового по среднему времени')
plt.xlabel('Действия')
plt.ylabel('время')
plt.show();
Количество сессий на среднее время
# формируем таблицу по медианному времени и количеству уникальных сессий
session_id_average_time = session_id_contacts_show_first.groupby('event_first')\
.agg({'minutes': ['mean'], 'session_id':['nunique']})
# преобразовываем MultiIndex в Index
session_id_average_time.columns = session_id_average_time.columns.map(''.join)
# меняем название столбцов
session_id_average_time.rename(columns={'minutesmean': 'mean', 'session_idnunique': 'session_id_count'}, inplace = True)
session_id_average_time.reset_index()
session_id_average_time.sort_values(by='mean', ascending=False)
#session_id_average_time
| mean | session_id_count | |
|---|---|---|
| event_first | ||
| tips_click | 26.086436 | 3 |
| map | 10.617769 | 178 |
| tips_show | 10.388056 | 486 |
| photos_show | 10.121990 | 156 |
| search | 8.656574 | 280 |
| favorites_add | 6.352063 | 18 |
| advert_open | 4.794377 | 39 |
# общее среднее время сессий от первого до Целевого по действиям
session_id_average_time['mean'].sum()
77.01726328016005
Среднее время от первого действия до Целового составляет 10 минут.Чтобы дойти до Целевого действия больше всего времени уходит на tips_click(клик по рекомендованному объявлению) 26 минут. На map(открыл карту объявления) , tips_show(увидел рекомендованное объявление) и photos_show(посмотрел фотографии объявлений) тратится 10 минут. Меньше всего времени уходит advert_open(открыл карточки объявления)- 5 минут. Почему так долго пользователь находится в tips_click?
# создаем таблицу с первыми данными по contacts_show
first_contacts_show = mobile_dataset.query('event == "contacts_show"').groupby('user_id')['time'].min().reset_index()
first_contacts_show.columns = ['user_id', 'time_first_contacts_show']
first_contacts_show.head()
| user_id | time_first_contacts_show | |
|---|---|---|
| 0 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 2019-10-20 19:17:18.659799 |
| 1 | 00551e79-152e-4441-9cf7-565d7eb04090 | 2019-10-25 16:44:41.263364 |
| 2 | 005fbea5-2678-406f-88a6-fbe9787e2268 | 2019-10-11 11:22:54.442841 |
| 3 | 00753c79-ea81-4456-acd0-a47a23ca2fb9 | 2019-10-20 14:57:06.080501 |
| 4 | 007d031d-5018-4e02-b7ee-72a30609173f | 2019-10-22 13:08:09.140381 |
# создаем данные по первому действию
event_first_time_min = mobile_dataset.sort_values(by=['user_id', 'time'])
# разделяем в таблице данные на начало и конец
event_first_time_min =event_first_time_min.groupby('user_id').agg({'time': ['min'],'event': ['first']})
# преобразовываем MultiIndex в Index
event_first_time_min.columns = event_first_time_min.columns.map(''.join)
# меняем название столбцов
event_first_time_min.rename(columns={'timemin': 'time_min', 'eventfirst': 'event_first'}, inplace = True)
event_first_time_min.reset_index().head()
| user_id | time_min | event_first | |
|---|---|---|---|
| 0 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-07 13:39:45.989359 | tips_show |
| 1 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 2019-10-19 21:34:33.849769 | search |
| 2 | 00463033-5717-4bf1-91b4-09183923b9df | 2019-11-01 13:54:35.385028 | photos_show |
| 3 | 004690c3-5a84-4bb7-a8af-e0c8f8fca64e | 2019-10-18 22:14:05.555052 | search |
| 4 | 00551e79-152e-4441-9cf7-565d7eb04090 | 2019-10-25 16:44:41.263364 | contacts_show |
# соединяем таблицы с первыми данными по contacts_show и данные по первому действию
user_id_contacts_show_first = first_contacts_show.merge(event_first_time_min, on='user_id', how='left')
user_id_contacts_show_first
| user_id | time_first_contacts_show | time_min | event_first | |
|---|---|---|---|---|
| 0 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 2019-10-20 19:17:18.659799 | 2019-10-19 21:34:33.849769 | search |
| 1 | 00551e79-152e-4441-9cf7-565d7eb04090 | 2019-10-25 16:44:41.263364 | 2019-10-25 16:44:41.263364 | contacts_show |
| 2 | 005fbea5-2678-406f-88a6-fbe9787e2268 | 2019-10-11 11:22:54.442841 | 2019-10-11 11:22:54.442841 | contacts_show |
| 3 | 00753c79-ea81-4456-acd0-a47a23ca2fb9 | 2019-10-20 14:57:06.080501 | 2019-10-20 14:57:06.080501 | contacts_show |
| 4 | 007d031d-5018-4e02-b7ee-72a30609173f | 2019-10-22 13:08:09.140381 | 2019-10-22 13:02:26.636223 | map |
| ... | ... | ... | ... | ... |
| 976 | fee3ba1c-16f4-46f7-bf56-4bf80cc4e2f5 | 2019-10-26 12:13:19.781554 | 2019-10-26 11:43:11.921954 | search |
| 977 | ff1554b5-919e-40b1-90bb-ee1f7f6d5846 | 2019-10-21 10:59:23.944891 | 2019-10-21 08:28:33.429234 | photos_show |
| 978 | ffc01466-fdb1-4460-ae94-e800f52eb136 | 2019-10-07 20:33:42.135500 | 2019-10-07 20:32:49.997044 | photos_show |
| 979 | ffe68f10-e48e-470e-be9b-eeb93128ff1a | 2019-10-22 16:07:17.683553 | 2019-10-21 16:39:33.867145 | search |
| 980 | fffb9e79-b927-4dbb-9b48-7fd09b23a62b | 2019-10-16 12:57:40.598641 | 2019-10-12 00:57:21.241896 | tips_show |
981 rows × 4 columns
# считаем количество дней
user_id_contacts_show_first['delta_time'] = user_id_contacts_show_first['time_first_contacts_show'] - user_id_contacts_show_first['time_min']
user_id_contacts_show_first.sort_values(by='delta_time').head()
| user_id | time_first_contacts_show | time_min | event_first | delta_time | |
|---|---|---|---|---|---|
| 302 | 4e62e09e-ef99-4c52-b8f6-ec50b42f6d56 | 2019-10-16 12:53:01.602333 | 2019-10-16 12:53:01.602333 | contacts_show | 0 days |
| 480 | 7f38ea5d-ad7c-4e8a-b1e1-c63da89a4c4c | 2019-10-16 17:07:15.556563 | 2019-10-16 17:07:15.556563 | contacts_show | 0 days |
| 198 | 2fb6515e-a902-4329-8080-749bcf4f8053 | 2019-10-31 00:35:18.082028 | 2019-10-31 00:35:18.082028 | contacts_show | 0 days |
| 200 | 30a6b55e-a72a-40bc-bbb9-7cc28a2e0b3e | 2019-10-11 14:03:59.819208 | 2019-10-11 14:03:59.819208 | contacts_show | 0 days |
| 474 | 7cc38f4c-7d5e-4dd4-a246-ef1ee2187da5 | 2019-10-19 15:18:30.743570 | 2019-10-19 15:18:30.743570 | contacts_show | 0 days |
# считаем количество пустых значений(contacts_show)
user_id_contacts_show_first.query('delta_time == "0 days 00:00:00"').count()
user_id 179 time_first_contacts_show 179 time_min 179 event_first 179 delta_time 179 dtype: int64
# удалаем пустые значения
user_id_contacts_show_first = user_id_contacts_show_first.query('delta_time != "0 days 00:00:00"')
user_id_contacts_show_first.head()
| user_id | time_first_contacts_show | time_min | event_first | delta_time | |
|---|---|---|---|---|---|
| 0 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 2019-10-20 19:17:18.659799 | 2019-10-19 21:34:33.849769 | search | 0 days 21:42:44.810030 |
| 4 | 007d031d-5018-4e02-b7ee-72a30609173f | 2019-10-22 13:08:09.140381 | 2019-10-22 13:02:26.636223 | map | 0 days 00:05:42.504158 |
| 6 | 0103a07d-513f-42b9-8d91-d5891d5655fe | 2019-11-03 18:50:04.133976 | 2019-11-03 18:42:59.275533 | tips_show | 0 days 00:07:04.858443 |
| 7 | 01556e76-d389-43bd-9fc7-1a3ba9802f49 | 2019-10-10 16:09:38.534928 | 2019-10-10 16:09:12.170942 | photos_show | 0 days 00:00:26.363986 |
| 8 | 01585246-7d9b-4e03-a465-095d5b891b0a | 2019-10-17 16:21:56.290598 | 2019-10-17 16:20:58.368137 | search | 0 days 00:00:57.922461 |
# количество действий до просмотра контактов
user_id_contacts_show_first.groupby('event_first').agg({'user_id': 'nunique'})\
.sort_values(by='user_id', ascending=False).reset_index()
| event_first | user_id | |
|---|---|---|
| 0 | tips_show | 260 |
| 1 | search | 209 |
| 2 | photos_show | 162 |
| 3 | map | 140 |
| 4 | favorites_add | 17 |
| 5 | advert_open | 12 |
| 6 | tips_click | 2 |
# строим распределение количество событий до ЦС
user_id_contacts_show_first.groupby('event_first').agg({'user_id': 'count'}).sort_values(by='user_id', ascending=False)\
.plot(figsize=(15, 5), grid=True)#.reset_index().plot(figsize=(15, 5), grid=True)
plt.title('Распределение количество событий до ЦС')
plt.xlabel('Действия')
plt.ylabel('Количество')
plt.show();
До просмотра контактов пользователь совершает следующие действия: tips_show(увидел рекомендованное объявление)- 260, search(выполнил поиск по сайту) - 209, photos_show(посмотрел фотографии) - 162, map(отpкрыл карту объявлений) - 140, favorites_add (добавил объявление в избранное) - 17, advert_open(открыл карточку объявлений) - 12 и затем tips_click(кликнул по рекомендованному объвлению) - 2.
# создаем таблицу с первыми данными по contacts_show и advert_open
advert_open_contacts_show = table.sort_values(by=['session_id', 'time'])
advert_open_contacts_show = table.query('source == "contacts_show"').groupby('session_id')['time'].min().reset_index()
advert_open_contacts_show.columns = ['session_id', 'time_first']
advert_open_contacts_show.head()
| session_id | time_first | |
|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 |
| 1 | 8 | 2019-10-29 21:26:40.258472 |
| 2 | 9 | 2019-10-30 08:01:05.420773 |
| 3 | 10 | 2019-11-03 17:12:09.708771 |
| 4 | 18 | 2019-10-25 16:44:41.263364 |
# создаем данные по первому действию advert_open и contacts_show
session_id_event_first_time_min1 = table.query('source == "advert_open" or source == "contacts_show"').sort_values(by=['session_id', 'time'])
# разделяем в таблице данные на начало и конец
session_id_event_first_time_min1 =session_id_event_first_time_min1.groupby('session_id').agg({'time': ['min'],'source': ['first']})
# преобразовываем MultiIndex в Index
session_id_event_first_time_min1.columns = session_id_event_first_time_min1.columns.map(''.join)
# меняем название столбцов
session_id_event_first_time_min1.rename(columns={'timemin': 'time_min', 'sourcefirst': 'event_first'}, inplace = True)
session_id_event_first_time_min1.reset_index().head()
| session_id | time_min | event_first | |
|---|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 | contacts_show |
| 1 | 7 | 2019-10-24 10:52:18.644065 | advert_open |
| 2 | 8 | 2019-10-29 21:26:40.258472 | contacts_show |
| 3 | 9 | 2019-10-30 08:01:05.420773 | contacts_show |
| 4 | 10 | 2019-11-03 17:12:09.708771 | contacts_show |
# соединяем таблицы
advert_open_contacts_show_min = advert_open_contacts_show.merge(session_id_event_first_time_min1, on='session_id', how='left')
advert_open_contacts_show_min.head()
| session_id | time_first | time_min | event_first | |
|---|---|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 | 2019-10-20 19:17:18.659799 | contacts_show |
| 1 | 8 | 2019-10-29 21:26:40.258472 | 2019-10-29 21:26:40.258472 | contacts_show |
| 2 | 9 | 2019-10-30 08:01:05.420773 | 2019-10-30 08:01:05.420773 | contacts_show |
| 3 | 10 | 2019-11-03 17:12:09.708771 | 2019-11-03 17:12:09.708771 | contacts_show |
| 4 | 18 | 2019-10-25 16:44:41.263364 | 2019-10-25 16:44:41.263364 | contacts_show |
# считаем количество дней
advert_open_contacts_show_min['delta_time'] = advert_open_contacts_show_min['time_first'] - advert_open_contacts_show_min['time_min']
advert_open_contacts_show_min.sort_values(by='delta_time').head()
| session_id | time_first | time_min | event_first | delta_time | |
|---|---|---|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 | 2019-10-20 19:17:18.659799 | contacts_show | 0 days |
| 1130 | 6957 | 2019-10-18 14:23:02.984261 | 2019-10-18 14:23:02.984261 | contacts_show | 0 days |
| 1129 | 6952 | 2019-10-14 14:58:59.584664 | 2019-10-14 14:58:59.584664 | contacts_show | 0 days |
| 1128 | 6951 | 2019-10-26 19:22:23.859482 | 2019-10-26 19:22:23.859482 | contacts_show | 0 days |
| 1127 | 6945 | 2019-10-11 16:53:15.400943 | 2019-10-11 16:53:15.400943 | contacts_show | 0 days |
advert_open_contacts_show_min['session_id'].nunique()
1703
# считаем количество пустых значений(contacts_show)
advert_open_contacts_show_min.query('delta_time == "0 days 00:00:00"').count()
session_id 1606 time_first 1606 time_min 1606 event_first 1606 delta_time 1606 dtype: int64
# удалаем пустые значения
advert_open_contacts_show_min = advert_open_contacts_show_min.query('delta_time != "0 days 00:00:00"')
advert_open_contacts_show_min.head()
| session_id | time_first | time_min | event_first | delta_time | |
|---|---|---|---|---|---|
| 30 | 151 | 2019-10-13 17:37:33.310985 | 2019-10-13 17:35:48.228892 | advert_open | 0 days 00:01:45.082093 |
| 31 | 154 | 2019-10-18 20:05:30.849432 | 2019-10-18 19:56:21.272541 | advert_open | 0 days 00:09:09.576891 |
| 38 | 183 | 2019-10-22 16:36:48.062582 | 2019-10-22 16:33:06.841621 | advert_open | 0 days 00:03:41.220961 |
| 49 | 280 | 2019-10-08 14:14:01.926825 | 2019-10-08 13:57:12.506935 | advert_open | 0 days 00:16:49.419890 |
| 66 | 344 | 2019-10-17 14:29:55.599850 | 2019-10-17 14:29:02.497882 | advert_open | 0 days 00:00:53.101968 |
# время проведенное между действиями
advert_open_contacts_show_min.describe()
| session_id | delta_time | |
|---|---|---|
| count | 97.000000 | 97 |
| mean | 4929.701031 | 0 days 00:08:01.630865659 |
| std | 2878.669442 | 0 days 00:12:21.550018710 |
| min | 151.000000 | 0 days 00:00:02.744644 |
| 25% | 2246.000000 | 0 days 00:01:00.977199 |
| 50% | 5282.000000 | 0 days 00:02:57.775595 |
| 75% | 7103.000000 | 0 days 00:09:51.529992 |
| max | 10252.000000 | 0 days 01:10:51.030238 |
# переведем дни в минуты
advert_open_contacts_show_min['minutes'] = advert_open_contacts_show_min['delta_time']/np.timedelta64(1,'m')
advert_open_contacts_show_min.head()
| session_id | time_first | time_min | event_first | delta_time | minutes | |
|---|---|---|---|---|---|---|
| 30 | 151 | 2019-10-13 17:37:33.310985 | 2019-10-13 17:35:48.228892 | advert_open | 0 days 00:01:45.082093 | 1.751368 |
| 31 | 154 | 2019-10-18 20:05:30.849432 | 2019-10-18 19:56:21.272541 | advert_open | 0 days 00:09:09.576891 | 9.159615 |
| 38 | 183 | 2019-10-22 16:36:48.062582 | 2019-10-22 16:33:06.841621 | advert_open | 0 days 00:03:41.220961 | 3.687016 |
| 49 | 280 | 2019-10-08 14:14:01.926825 | 2019-10-08 13:57:12.506935 | advert_open | 0 days 00:16:49.419890 | 16.823665 |
| 66 | 344 | 2019-10-17 14:29:55.599850 | 2019-10-17 14:29:02.497882 | advert_open | 0 days 00:00:53.101968 | 0.885033 |
# общее время от advert_open до contacts_show
advert_open_contacts_show_min['minutes'].sum()
778.63656615
# всего advert_open
advert_open_contacts_show_min['session_id'].nunique()
97
# среднее время от advert_open до contacts_show
advert_open_contacts_show_min['minutes'].sum()/ advert_open_contacts_show_min['session_id'].nunique()
8.027181094329897
Среднее время от advert_open до contacts_show составляет 8 минут. Медианное время - 3 минуты.
# создаем таблицу с первыми данными по advert_open
advert_open1 = unnecessary_things.sort_values(by=['user_id', 'time'])
advert_open1 = unnecessary_things.query('event == "advert_open"').groupby('user_id')['time'].min().reset_index()
advert_open1.columns = ['user_id', 'time_first']
advert_open1.head()
| user_id | time_first | |
|---|---|---|
| 0 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 2019-10-24 10:52:18.644065 |
| 1 | 004690c3-5a84-4bb7-a8af-e0c8f8fca64e | 2019-10-27 00:08:28.225172 |
| 2 | 00b59e77-3dc8-4193-a217-c50b9fe849bf | 2019-10-29 15:28:18.987414 |
| 3 | 0164902d-7393-47e1-9d5b-0ec4c0171cdc | 2019-10-28 15:16:57.266458 |
| 4 | 017c6afc-965d-4c94-84ee-f0e326998e30 | 2019-10-10 14:29:11.294613 |
# создаем данные по первому действию unnecessary_things и contacts_show
session_id_event_first_time_min17 = unnecessary_things.query('event == "contacts_show"').sort_values(by=['user_id', 'time'])
# разделяем в таблице данные на начало и конец
session_id_event_first_time_min17 =session_id_event_first_time_min17.groupby('user_id')\
.agg({'time': ['min'],'event': ['first']})
# преобразовываем MultiIndex в Index
session_id_event_first_time_min17.columns = session_id_event_first_time_min17.columns.map(''.join)
# меняем название столбцов
session_id_event_first_time_min17.rename(columns={'timemin': 'time_min', 'eventfirst': 'event_first'}, inplace = True)
session_id_event_first_time_min17.reset_index().head()
| user_id | time_min | event_first | |
|---|---|---|---|
| 0 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 2019-10-20 19:17:18.659799 | contacts_show |
| 1 | 00551e79-152e-4441-9cf7-565d7eb04090 | 2019-10-25 16:44:41.263364 | contacts_show |
| 2 | 005fbea5-2678-406f-88a6-fbe9787e2268 | 2019-10-11 11:22:54.442841 | contacts_show |
| 3 | 00753c79-ea81-4456-acd0-a47a23ca2fb9 | 2019-10-20 14:57:06.080501 | contacts_show |
| 4 | 007d031d-5018-4e02-b7ee-72a30609173f | 2019-10-22 13:08:09.140381 | contacts_show |
# соединяем таблицы
advert_open_contacts_show_min1 = advert_open1.merge(session_id_event_first_time_min17, on='user_id' , how='left')
advert_open_contacts_show_min1.head()
| user_id | time_first | time_min | event_first | |
|---|---|---|---|---|
| 0 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 2019-10-24 10:52:18.644065 | 2019-10-20 19:17:18.659799 | contacts_show |
| 1 | 004690c3-5a84-4bb7-a8af-e0c8f8fca64e | 2019-10-27 00:08:28.225172 | NaT | NaN |
| 2 | 00b59e77-3dc8-4193-a217-c50b9fe849bf | 2019-10-29 15:28:18.987414 | NaT | NaN |
| 3 | 0164902d-7393-47e1-9d5b-0ec4c0171cdc | 2019-10-28 15:16:57.266458 | NaT | NaN |
| 4 | 017c6afc-965d-4c94-84ee-f0e326998e30 | 2019-10-10 14:29:11.294613 | NaT | NaN |
advert_open_contacts_show_min1['user_id'].nunique()
751
advert_open_contacts_show_min1['event_first'].value_counts()
contacts_show 138 Name: event_first, dtype: int64
# рассчитываем конверсию В Цeлевое действие ADVERT_OPEN -> CONTACTS_SHOW
con_adv1 = advert_open_contacts_show_min1\
.groupby('event_first').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False)
con_adv1['advert_open'] = advert_open_contacts_show_min1['user_id'].nunique()
con_adv1['conversion'] = round(con_adv1.user_id / con_adv1.advert_open * 100, 2)
con_adv1.reset_index()
| event_first | user_id | advert_open | conversion | |
|---|---|---|---|---|
| 0 | contacts_show | 138 | 751 | 18.38 |
Конверсия В Цeлевое действие ADVERT_OPEN(751) -> CONTACTS_SHOW(138) составляет 18.38%
# создаем таблицу с первыми данными по contacts_show и tips_click
contacts_show1 = table.sort_values(by=['session_id', 'time'])
contacts_show1 = table.query('source == "contacts_show"').groupby('session_id')['time'].min().reset_index()
contacts_show1.columns = ['session_id', 'time_first']
contacts_show1.head()
| session_id | time_first | |
|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 |
| 1 | 8 | 2019-10-29 21:26:40.258472 |
| 2 | 9 | 2019-10-30 08:01:05.420773 |
| 3 | 10 | 2019-11-03 17:12:09.708771 |
| 4 | 18 | 2019-10-25 16:44:41.263364 |
# создаем данные по первому действию tips_click и contacts_show
session_id_tips_click_first_time_min = table.query('source == "tips_click" or source == "contacts_show"').sort_values(by=['session_id', 'time'])
# разделяем в таблице данные на начало и конец
session_id_tips_click_first_time_min =session_id_tips_click_first_time_min.groupby('session_id')\
.agg({'time': ['min'],'source': ['first']})
# преобразовываем MultiIndex в Index
session_id_tips_click_first_time_min.columns = session_id_event_first_time_min1.columns.map(''.join)
# меняем название столбцов
session_id_tips_click_first_time_min.rename(columns={'timemin': 'time_min', 'sourcefirst': 'event_first'}, inplace = True)
session_id_tips_click_first_time_min.reset_index().head()
| session_id | time_min | event_first | |
|---|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 | contacts_show |
| 1 | 8 | 2019-10-29 21:26:40.258472 | contacts_show |
| 2 | 9 | 2019-10-30 08:01:05.420773 | contacts_show |
| 3 | 10 | 2019-11-03 17:12:09.708771 | contacts_show |
| 4 | 18 | 2019-10-25 16:44:41.263364 | contacts_show |
# соединяем таблицы
tips_click_contacts_show_min = contacts_show1.merge(session_id_tips_click_first_time_min, on='session_id', how='left')
tips_click_contacts_show_min.head()
| session_id | time_first | time_min | event_first | |
|---|---|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 | 2019-10-20 19:17:18.659799 | contacts_show |
| 1 | 8 | 2019-10-29 21:26:40.258472 | 2019-10-29 21:26:40.258472 | contacts_show |
| 2 | 9 | 2019-10-30 08:01:05.420773 | 2019-10-30 08:01:05.420773 | contacts_show |
| 3 | 10 | 2019-11-03 17:12:09.708771 | 2019-11-03 17:12:09.708771 | contacts_show |
| 4 | 18 | 2019-10-25 16:44:41.263364 | 2019-10-25 16:44:41.263364 | contacts_show |
# считаем количество дней
tips_click_contacts_show_min['delta_time'] = tips_click_contacts_show_min['time_first'] - tips_click_contacts_show_min['time_min']
tips_click_contacts_show_min.sort_values(by='delta_time').head()
| session_id | time_first | time_min | event_first | delta_time | |
|---|---|---|---|---|---|
| 0 | 6 | 2019-10-20 19:17:18.659799 | 2019-10-20 19:17:18.659799 | contacts_show | 0 days |
| 1135 | 6988 | 2019-11-03 11:43:34.001832 | 2019-11-03 11:43:34.001832 | contacts_show | 0 days |
| 1134 | 6981 | 2019-10-20 20:28:43.550386 | 2019-10-20 20:28:43.550386 | contacts_show | 0 days |
| 1133 | 6979 | 2019-10-31 12:26:23.730292 | 2019-10-31 12:26:23.730292 | contacts_show | 0 days |
| 1132 | 6978 | 2019-10-14 10:44:46.915801 | 2019-10-14 10:44:46.915801 | contacts_show | 0 days |
# считаем количество пустых значений(contacts_show)
tips_click_contacts_show_min.query('delta_time == "0 days 00:00:00"').count()
session_id 1675 time_first 1675 time_min 1675 event_first 1675 delta_time 1675 dtype: int64
# удалаем пустые значения
tips_click_contacts_show_min = tips_click_contacts_show_min.query('delta_time != "0 days 00:00:00"')
tips_click_contacts_show_min.head()
| session_id | time_first | time_min | event_first | delta_time | |
|---|---|---|---|---|---|
| 97 | 538 | 2019-10-13 23:02:17.711053 | 2019-10-13 22:33:18.694326 | tips_click | 0 days 00:28:59.016727 |
| 179 | 1152 | 2019-10-31 14:01:32.965861 | 2019-10-31 13:52:04.994469 | tips_click | 0 days 00:09:27.971392 |
| 232 | 1498 | 2019-10-23 14:36:01.009189 | 2019-10-23 14:13:38.969895 | tips_click | 0 days 00:22:22.039294 |
| 239 | 1557 | 2019-10-07 11:11:50.979295 | 2019-10-07 11:07:14.538207 | tips_click | 0 days 00:04:36.441088 |
| 243 | 1565 | 2019-10-14 07:24:01.706966 | 2019-10-14 07:19:52.620198 | tips_click | 0 days 00:04:09.086768 |
# время проведенное между действиями
tips_click_contacts_show_min.describe()
| session_id | delta_time | |
|---|---|---|
| count | 28.000000 | 28 |
| mean | 5155.357143 | 0 days 00:13:35.650209500 |
| std | 2976.691156 | 0 days 00:13:28.621342675 |
| min | 538.000000 | 0 days 00:00:17.448593 |
| 25% | 1904.250000 | 0 days 00:03:55.232924500 |
| 50% | 4889.000000 | 0 days 00:07:50.879688 |
| 75% | 7499.250000 | 0 days 00:19:54.083226250 |
| max | 10022.000000 | 0 days 00:50:47.995941 |
# переведем дни в минуты
tips_click_contacts_show_min['minutes'] = tips_click_contacts_show_min['delta_time']/np.timedelta64(1,'m')
tips_click_contacts_show_min.head()
| session_id | time_first | time_min | event_first | delta_time | minutes | |
|---|---|---|---|---|---|---|
| 97 | 538 | 2019-10-13 23:02:17.711053 | 2019-10-13 22:33:18.694326 | tips_click | 0 days 00:28:59.016727 | 28.983612 |
| 179 | 1152 | 2019-10-31 14:01:32.965861 | 2019-10-31 13:52:04.994469 | tips_click | 0 days 00:09:27.971392 | 9.466190 |
| 232 | 1498 | 2019-10-23 14:36:01.009189 | 2019-10-23 14:13:38.969895 | tips_click | 0 days 00:22:22.039294 | 22.367322 |
| 239 | 1557 | 2019-10-07 11:11:50.979295 | 2019-10-07 11:07:14.538207 | tips_click | 0 days 00:04:36.441088 | 4.607351 |
| 243 | 1565 | 2019-10-14 07:24:01.706966 | 2019-10-14 07:19:52.620198 | tips_click | 0 days 00:04:09.086768 | 4.151446 |
# общее время от tips_click до contacts_show
tips_click_contacts_show_min['minutes'].sum()
380.6367644333334
# всего tips_click
tips_click_contacts_show_min['session_id'].nunique()
28
# среднее время от tips_click до contacts_show
tips_click_contacts_show_min['minutes'].sum()/ tips_click_contacts_show_min['session_id'].nunique()
13.594170158333336
Среднее время от tips_click до contacts_show составляет 13 минут. Медианное время - 8 минут.
# создаем таблицу с первыми данными по tips_click
tips_click_user = unnecessary_things.sort_values(by=['user_id', 'time'])
tips_click_user = unnecessary_things.query('event == "tips_click"').groupby('user_id')['time'].min().reset_index()
tips_click_user.columns = ['user_id', 'time_first']
tips_click_user.head()
| user_id | time_first | |
|---|---|---|
| 0 | 01147bf8-cd48-49c0-a5af-3f6eb45f8262 | 2019-11-01 22:20:20.323377 |
| 1 | 01b4ca51-930d-4518-aa09-8a8c35e1d9cc | 2019-10-22 11:22:12.442909 |
| 2 | 02ac1c07-7a45-4d4b-9dbe-bea066039c01 | 2019-10-14 10:47:22.491517 |
| 3 | 02e7c193-842b-4995-b67a-8c87ac0f29bb | 2019-10-08 21:03:01.344918 |
| 4 | 034a556c-8837-4c78-8012-795e03764657 | 2019-11-03 17:05:31.274371 |
# создаем данные по первому действию unnecessary_things и contacts_show
session_id_event_first_time_min17 = unnecessary_things.query('event == "contacts_show"').sort_values(by=['user_id', 'time'])
# разделяем в таблице данные на начало и конец
session_id_event_first_time_min17 =session_id_event_first_time_min17.groupby('user_id')\
.agg({'time': ['min'],'event': ['first']})
# преобразовываем MultiIndex в Index
session_id_event_first_time_min17.columns = session_id_event_first_time_min17.columns.map(''.join)
# меняем название столбцов
session_id_event_first_time_min17.rename(columns={'timemin': 'time_min', 'eventfirst': 'event_first'}, inplace = True)
session_id_event_first_time_min17.reset_index().head()
| user_id | time_min | event_first | |
|---|---|---|---|
| 0 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 2019-10-20 19:17:18.659799 | contacts_show |
| 1 | 00551e79-152e-4441-9cf7-565d7eb04090 | 2019-10-25 16:44:41.263364 | contacts_show |
| 2 | 005fbea5-2678-406f-88a6-fbe9787e2268 | 2019-10-11 11:22:54.442841 | contacts_show |
| 3 | 00753c79-ea81-4456-acd0-a47a23ca2fb9 | 2019-10-20 14:57:06.080501 | contacts_show |
| 4 | 007d031d-5018-4e02-b7ee-72a30609173f | 2019-10-22 13:08:09.140381 | contacts_show |
# соединяем таблицы
tips_click_contacts_show_min1 = tips_click_user.merge(session_id_event_first_time_min17, on='user_id' , how='left')
tips_click_contacts_show_min1.head()
| user_id | time_first | time_min | event_first | |
|---|---|---|---|---|
| 0 | 01147bf8-cd48-49c0-a5af-3f6eb45f8262 | 2019-11-01 22:20:20.323377 | NaT | NaN |
| 1 | 01b4ca51-930d-4518-aa09-8a8c35e1d9cc | 2019-10-22 11:22:12.442909 | NaT | NaN |
| 2 | 02ac1c07-7a45-4d4b-9dbe-bea066039c01 | 2019-10-14 10:47:22.491517 | NaT | NaN |
| 3 | 02e7c193-842b-4995-b67a-8c87ac0f29bb | 2019-10-08 21:03:01.344918 | NaT | NaN |
| 4 | 034a556c-8837-4c78-8012-795e03764657 | 2019-11-03 17:05:31.274371 | NaT | NaN |
tips_click_contacts_show_min1['user_id'].nunique()
322
# рассчитываем конверсию между уникальными пользователя contacts_show и advert_open
con_tips1 = tips_click_contacts_show_min1\
.groupby('event_first').agg({'user_id': 'nunique'}).sort_values(by='user_id', ascending=False)
con_tips1['tips_click'] = tips_click_contacts_show_min1['user_id'].nunique()
con_tips1['conversion'] = round(con_tips1.user_id / con_tips1.tips_click * 100, 2)
con_tips1.reset_index()
| event_first | user_id | tips_click | conversion | |
|---|---|---|---|---|
| 0 | contacts_show | 100 | 322 | 31.06 |
Конверсия в Целевое действие TIPS_CLICK(322)-> CONTACTS_SHOW(100) составляет - 31.06%.
Гипотеза H0: между конверсиями действий tips_show + tips_click и tips_show нет значимой разницы
Гипотеза H1: между конверсиями есть значимая разница
# создаем таблицу с первыми данными по tips_click
tips_click_user = unnecessary_things.sort_values(by=['user_id', 'time'])
tips_click_user = unnecessary_things.query('event == "tips_click"').groupby('user_id')['time'].min().reset_index()
tips_click_user.columns = ['user_id', 'time_first']
tips_click_user.head()
| user_id | time_first | |
|---|---|---|
| 0 | 01147bf8-cd48-49c0-a5af-3f6eb45f8262 | 2019-11-01 22:20:20.323377 |
| 1 | 01b4ca51-930d-4518-aa09-8a8c35e1d9cc | 2019-10-22 11:22:12.442909 |
| 2 | 02ac1c07-7a45-4d4b-9dbe-bea066039c01 | 2019-10-14 10:47:22.491517 |
| 3 | 02e7c193-842b-4995-b67a-8c87ac0f29bb | 2019-10-08 21:03:01.344918 |
| 4 | 034a556c-8837-4c78-8012-795e03764657 | 2019-11-03 17:05:31.274371 |
tips_click_user['user_id'].nunique()
322
# создаем таблицу с первыми данными по tips_show
tips_show_user = unnecessary_things.sort_values(by=['user_id', 'time'])
tips_show_user = unnecessary_things.query('event == "tips_show"').groupby('user_id')['time'].min().reset_index()
tips_show_user.columns = ['user_id', 'time_first']
tips_show_user.head()
| user_id | time_first | |
|---|---|---|
| 0 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-07 13:39:45.989359 |
| 1 | 004690c3-5a84-4bb7-a8af-e0c8f8fca64e | 2019-10-20 17:48:42.169252 |
| 2 | 00554293-7e00-4122-b898-4e892c4a7c53 | 2019-10-27 12:33:13.354542 |
| 3 | 005fbea5-2678-406f-88a6-fbe9787e2268 | 2019-10-11 11:29:10.270892 |
| 4 | 007d031d-5018-4e02-b7ee-72a30609173f | 2019-10-22 13:04:20.485500 |
tips_show_user['user_id'].nunique()
2801
# соединяем таблицы
tips_click_tips_show_user = tips_click_user.merge(tips_show_user, on='user_id')
tips_click_tips_show_user.head()
| user_id | time_first_x | time_first_y | |
|---|---|---|---|
| 0 | 01147bf8-cd48-49c0-a5af-3f6eb45f8262 | 2019-11-01 22:20:20.323377 | 2019-11-01 21:13:53.377582 |
| 1 | 01b4ca51-930d-4518-aa09-8a8c35e1d9cc | 2019-10-22 11:22:12.442909 | 2019-10-22 11:06:21.296203 |
| 2 | 02e7c193-842b-4995-b67a-8c87ac0f29bb | 2019-10-08 21:03:01.344918 | 2019-10-18 23:56:01.687840 |
| 3 | 04ee0c31-3c77-49f8-81d2-bbfe9fe2cde8 | 2019-10-23 16:09:25.426033 | 2019-10-23 16:08:52.128563 |
| 4 | 0656a1d1-9032-43ae-b936-11e41526eeff | 2019-10-27 18:55:52.567024 | 2019-10-27 18:48:21.967904 |
# уникальные пользователи tips_click+tips_show
tips_click_tips_show_user['user_id'].nunique()
297
# создаем данные по первому действию tips_click+tips_show и contacts_show
contacts_show_user = unnecessary_things.query('event == "contacts_show"').sort_values(by=['user_id', 'time'])
# разделяем в таблице данные на начало и конец
contacts_show_user =contacts_show_user.groupby('user_id').agg({'time': ['min'],'event': ['first']})
# преобразовываем MultiIndex в Index
contacts_show_user.columns = contacts_show_user.columns.map(''.join)
# меняем название столбцов
contacts_show_user.rename(columns={'timemin': 'time_min', 'eventfirst': 'event'}, inplace = True)
contacts_show_user.reset_index().head()
| user_id | time_min | event | |
|---|---|---|---|
| 0 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 2019-10-20 19:17:18.659799 | contacts_show |
| 1 | 00551e79-152e-4441-9cf7-565d7eb04090 | 2019-10-25 16:44:41.263364 | contacts_show |
| 2 | 005fbea5-2678-406f-88a6-fbe9787e2268 | 2019-10-11 11:22:54.442841 | contacts_show |
| 3 | 00753c79-ea81-4456-acd0-a47a23ca2fb9 | 2019-10-20 14:57:06.080501 | contacts_show |
| 4 | 007d031d-5018-4e02-b7ee-72a30609173f | 2019-10-22 13:08:09.140381 | contacts_show |
# соединяем таблицы
tips_click_tips_show_user1 = tips_click_tips_show_user.merge(contacts_show_user, on='user_id')
tips_click_tips_show_user1.rename(columns={'eventfirst': 'event'}, inplace = True)
tips_click_tips_show_user1.head()
| user_id | time_first_x | time_first_y | time_min | event | |
|---|---|---|---|---|---|
| 0 | 06bdb96e-2712-47b3-a0af-d19f297abd6c | 2019-10-12 18:16:02.627749 | 2019-10-12 17:36:57.950995 | 2019-10-12 17:52:29.183855 | contacts_show |
| 1 | 081bb564-703e-4f1c-9016-ac9460dec5bf | 2019-10-28 18:35:40.638076 | 2019-10-28 17:10:47.969156 | 2019-10-29 18:02:08.808218 | contacts_show |
| 2 | 0d03c0d5-8463-40a5-a9bc-c688f7058ddb | 2019-10-13 22:33:18.694326 | 2019-10-13 22:28:19.121602 | 2019-10-13 23:02:17.711053 | contacts_show |
| 3 | 136b7b37-2bd4-4718-b14a-e38bc3d6d112 | 2019-10-17 23:49:11.455900 | 2019-10-07 21:33:19.333950 | 2019-10-07 21:38:02.944592 | contacts_show |
| 4 | 145009b0-bb47-4239-8afb-ad9a4b21ee2f | 2019-10-07 20:37:57.598909 | 2019-10-07 20:27:26.688851 | 2019-10-07 20:26:29.099318 | contacts_show |
# уникальные пользователи contacts_show в tips_click+tips_show
tips_click_tips_show_user1['user_id'].nunique()
91
# уникальные пользователи tips_click+tips_show
tips_click_tips_show_user['user_id'].nunique()
297
# собираем уникальных пользователей contacts_show в tips_click+tips_show
conversion_click_show = tips_click_tips_show_user1.pivot_table(index = 'event', values = 'user_id', aggfunc = 'nunique').sort_values(by='user_id', ascending=False)
# создаем столбец по уникальных пользователей tips_click+tips_show
conversion_click_show['click_show'] = tips_click_tips_show_user['user_id'].nunique()
# находим конверсию
conversion_click_show['conversion'] = round(conversion_click_show['user_id'] / conversion_click_show['click_show']*100,2)
conversion_click_show.reset_index()
| event | user_id | click_show | conversion | |
|---|---|---|---|---|
| 0 | contacts_show | 91 | 297 | 30.64 |
Конверсия в Целевое действие tips_show + tips_click(297)-> contacts_show(91) составляет - 30.64%.
#создаем таблицу с первыми данными по tips_show
tips_show_user = unnecessary_things.sort_values(by=['user_id', 'time'])
tips_show_user = unnecessary_things.query('event == "tips_show"').groupby('user_id')['time'].min().reset_index()
tips_show_user.columns = ['user_id', 'time_first']
tips_show_user.head()
| user_id | time_first | |
|---|---|---|
| 0 | 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 | 2019-10-07 13:39:45.989359 |
| 1 | 004690c3-5a84-4bb7-a8af-e0c8f8fca64e | 2019-10-20 17:48:42.169252 |
| 2 | 00554293-7e00-4122-b898-4e892c4a7c53 | 2019-10-27 12:33:13.354542 |
| 3 | 005fbea5-2678-406f-88a6-fbe9787e2268 | 2019-10-11 11:29:10.270892 |
| 4 | 007d031d-5018-4e02-b7ee-72a30609173f | 2019-10-22 13:04:20.485500 |
# уникальных пользователей tips_show
tips_show_user['user_id'].nunique()
2801
# создаем данные по первому действию contacts_show
contacts_show_user = unnecessary_things.query('event == "contacts_show"').sort_values(by=['user_id', 'time'])
# разделяем в таблице данные на начало и конец
contacts_show_user =contacts_show_user.groupby('user_id').agg({'time': ['min'],'event': ['first']})
# преобразовываем MultiIndex в Index
contacts_show_user.columns = contacts_show_user.columns.map(''.join)
# меняем название столбцов
contacts_show_user.rename(columns={'timemin': 'time_min', 'eventfirst': 'event_first'}, inplace = True)
contacts_show_user.reset_index().head()
| user_id | time_min | event_first | |
|---|---|---|---|
| 0 | 00157779-810c-4498-9e05-a1e9e3cedf93 | 2019-10-20 19:17:18.659799 | contacts_show |
| 1 | 00551e79-152e-4441-9cf7-565d7eb04090 | 2019-10-25 16:44:41.263364 | contacts_show |
| 2 | 005fbea5-2678-406f-88a6-fbe9787e2268 | 2019-10-11 11:22:54.442841 | contacts_show |
| 3 | 00753c79-ea81-4456-acd0-a47a23ca2fb9 | 2019-10-20 14:57:06.080501 | contacts_show |
| 4 | 007d031d-5018-4e02-b7ee-72a30609173f | 2019-10-22 13:08:09.140381 | contacts_show |
# соединяем таблицы
contact_show_user = tips_show_user.merge(contacts_show_user, on='user_id')
contact_show_user.head()
| user_id | time_first | time_min | event_first | |
|---|---|---|---|---|
| 0 | 005fbea5-2678-406f-88a6-fbe9787e2268 | 2019-10-11 11:29:10.270892 | 2019-10-11 11:22:54.442841 | contacts_show |
| 1 | 007d031d-5018-4e02-b7ee-72a30609173f | 2019-10-22 13:04:20.485500 | 2019-10-22 13:08:09.140381 | contacts_show |
| 2 | 0103a07d-513f-42b9-8d91-d5891d5655fe | 2019-11-03 18:42:59.275533 | 2019-11-03 18:50:04.133976 | contacts_show |
| 3 | 035ae717-a6ae-4569-b952-16be9447832b | 2019-10-27 16:52:54.870118 | 2019-10-27 16:58:12.318501 | contacts_show |
| 4 | 03739d15-7212-415c-9c88-4dfc24b8d3b5 | 2019-10-24 10:33:29.694673 | 2019-10-24 10:19:36.719134 | contacts_show |
# уникальных пользователей в tips_show - contacts_show
contact_show_user['user_id'].nunique()
516
# преобразования массива в список
contact_show_user = contact_show_user['user_id'].unique().tolist()
tips_click_tips_show_user1 = tips_click_tips_show_user1['user_id'].unique().tolist()
# уникальные пользователи tips_show (516-91) в contacts_show
not_click = list(set(contact_show_user) - set(tips_click_tips_show_user1))
# подставляем в таблицу
not_click_contact_user = unnecessary_things.query('user_id == @not_click')[['user_id', 'event']]
not_click_contact_user['user_id'].nunique()
425
# преобразования массива в список
tips_show_user1 = tips_show_user['user_id'].unique().tolist()
tips_click_tips_show_user1 = tips_click_tips_show_user['user_id'].unique().tolist()
# уникальные пользователи tips_show(2801-297)
not_tips_click1 = list(set(tips_show_user1) - set(tips_click_tips_show_user1))
# подставляем в таблицу
not_click_user = unnecessary_things.query('user_id == @not_tips_click1')[['user_id', 'event']]
not_click_user['user_id'].nunique()
2504
# собираем уникальных пользователей contacts_show в tips_click+tips_show
conversion_show = not_click_contact_user.query('event == "tips_show"').pivot_table(index = 'event', values = 'user_id', aggfunc = 'nunique').sort_values(by='user_id', ascending=False)
# создаем столбец по уникальных пользователей tips_click+tips_show
conversion_show['count'] = not_click_user['user_id'].nunique()
# находим конверсию
conversion_show['conversion'] = round(conversion_show['user_id'] / conversion_show['count']*100,2)
# переименовываем столбец
conversion_show.rename(columns={'user_id':'contacts_show'}, inplace = True)
conversion_show.reset_index()
| event | contacts_show | count | conversion | |
|---|---|---|---|---|
| 0 | tips_show | 425 | 2504 | 16.97 |
Конверсия в Целевое действие tips_show(2504) -> contacts_show(425) составляет - 16.97%.
tips_click_tips_show_user1 = unnecessary_things.query('user_id == @tips_click_tips_show_user1')[['user_id', 'event']]
def z_test(not_click_user, tips_click_tips_show_user):
# критический уровень статистической значимости
alpha = 0.05
# число пользователей в группе 1 и группе 2:
users_new = np.array([not_click_user['user_id'].nunique(),
tips_click_tips_show_user['user_id'].nunique()])
# число пользователей, совершивших событие в группе 1 и группе 2
success = np.array([not_click_user[not_click_user['event'] == 'contacts_show']['user_id'].nunique(),
tips_click_tips_show_user1[tips_click_tips_show_user1['event'] == 'contacts_show']['user_id'].nunique()])
# пропорции успехов в группах:
p1 = success[0]/users_new[0]
p2 = success[1]/users_new[1]
# пропорция успехов в комбинированном датасете:
p_combined = (success[0] + success[1]) / (users_new[0] + users_new[1])
# разница пропорций в датасетах
difference = p1 - p2
# считаем статистику в ст.отклонениях стандартного нормального распределения
z_value = difference / np.sqrt(p_combined * (1 - p_combined) * (1/users_new[0] + 1/users_new[1]))
# задаем стандартное нормальное распределение (среднее 0, ст.отклонение 1)
distr = st.norm(0, 1)
p_value = (1 - distr.cdf(abs(z_value))) * 2 #тест двусторонний, удваиваем результат
print('p-значение: ', p_value)
if p_value < alpha:
print('Отвергаем нулевую гипотезу: между конверсиями действий tips_show + tips_click и tips_show есть разница')
else:
print('Не получилось отвергнуть нулевую гипотезу, нет оснований считать конверсию действий tips_show + tips_click и tips_show разными.')
# считаем статистическую значимость между tips_click + tips_show и tips_show
z_test(not_click_user, tips_click_tips_show_user)
print()
p-значение: 9.218316554537864e-09 Отвергаем нулевую гипотезу: между конверсиями действий tips_show + tips_click и tips_show есть разница
Гипотеза H0: между конверсиями источников yandex и google нет значимой разницы
Гипотеза H1: между конверсиями есть значимая разница
# Сделам таблицу с числом уникальных пользователей по событиям и источникам yandex и google
yandex_google1 = unnecessary_things.query('source == "yandex" and event == "contacts_show" or source == "google" and event == "contacts_show"')
count_yandex_google = yandex_google1.pivot_table(index='source',columns = 'event', values='user_id',aggfunc='nunique').sort_values(by = 'source', ascending = True)
count_yandex_google['count'] = unnecessary_things.pivot_table(index = 'source', values = 'user_id', aggfunc = 'nunique').sort_values(by='user_id', ascending=False)
count_yandex_google['conversion'] = round(count_yandex_google['contacts_show'] / count_yandex_google['count']*100,2)
count_yandex_google.sort_values(by = 'source', ascending = False).reset_index()
| event | source | contacts_show | count | conversion |
|---|---|---|---|---|
| 0 | yandex | 478 | 1934 | 24.72 |
| 1 | 275 | 1129 | 24.36 |
google1 = unnecessary_things.query('source == "google"')
yandex1 = unnecessary_things.query('source == "yandex"')
google1['user_id'].nunique()
1129
yandex1['user_id'].nunique()
1934
google1[google1['event'] == 'contacts_show']['user_id'].nunique()
275
yandex1[yandex1['event'] == 'contacts_show']['user_id'].nunique()
478
def z_test(google1, yandex1):
# критический уровень статистической значимости
alpha = 0.05
# число пользователей в группе 1 и группе 2:
users_new = np.array([google1['user_id'].nunique(),
yandex1['user_id'].nunique()])
# число пользователей, совершивших событие в группе 1 и группе 2
success = np.array([google1[google1['event'] == 'contacts_show']['user_id'].nunique() ,
yandex1[yandex1['event'] == 'contacts_show']['user_id'].nunique()])
# пропорции успехов в группах:
p1 = success[0]/users_new[0]
p2 = success[1]/users_new[1]
# пропорция успехов в комбинированном датасете:
p_combined = (success[0] + success[1]) / (users_new[0] + users_new[1])
# разница пропорций в датасетах
difference = p1 - p2
# считаем статистику в ст.отклонениях стандартного нормального распределения
z_value = difference / np.sqrt(p_combined * (1 - p_combined) * (1/users_new[0] + 1/users_new[1]))
# задаем стандартное нормальное распределение (среднее 0, ст.отклонение 1)
distr = st.norm(0, 1)
p_value = (1 - distr.cdf(abs(z_value))) * 2 #тест двусторонний, удваиваем результат
print('p-значение: ', p_value)
if p_value < alpha:
print('Отвергаем нулевую гипотезу: между конверсиями есть разница')
else:
print('Не получилось отвергнуть нулевую гипотезу, нет оснований считать конверсию между источниками yandex и google разными.')
# считаем статистическую значимость между источниками yandex и google
z_test(google1, yandex1)
print()
p-значение: 0.8244316027993777 Не получилось отвергнуть нулевую гипотезу, нет оснований считать конверсию между источниками yandex и google разными.
Данные предоставлены за 28 дней с 07 октября по 03 ноября 2019 г. Всего в данных 74197 событий и 4293 пользователя. В среднем на пользователя приходится 17 событий. Пользователи предпочитают пользоваться приложение в буднии дни. В данных 3 источника:yandex(34286), google(20445), other(19466). В представленных данных 9 действий: tips_show(40055), photos_show(10012), search(6784), advert_open(6164), contacts_show(4529), map(3881), favorites_add(1417), tips_click(814), contacts_call(541). Retention Rate коэффициент удержания низкий 24% и соответсвенно Churn Rate коэффициент отскока высокий 76%. Количество уникальных пользователй в день DAU - 153. Количество уникальных пользователй в неделю WAU - 1073. Среднее время от первого действия до Целового составляет 10 минут.Чтобы дойти до Целевого действия больше всего времени уходит на tips_click(клик по рекомендованному объявлению) 26 минут. На map(открыл карту объявления) , tips_show(увидел рекомендованное объявление) и photos_show(посмотрел фотографии объявлений) тратится 10 минут. Меньше всего времени уходит advert_open(открыл карточки объявления)- 5 минут. До просмотра контактов пользователь совершает следующие действия: tips_show(увидел рекомендованное объявление)- 260, search(выполнил поиск по сайту) - 209, photos_show(посмотрел фотографии) - 162, map(отpкрыл карту объявлений) - 140, favorites_add (добавил объявление в избранное) - 17, advert_open(открыл карточку объявлений) - 12 и затем tips_click(кликнул по рекомендованному объвлению) - 2.
Пользователи доходят до Целевого действия через поиск по приложению:search(100%)-tips_show(48%)-contacts_show(47%). Через открытия карточки объявления до целевого действия: advert_open(100%) - tips_show(79%)-contacts_show(23%). Пользователи доходят до Целевого действия через открытие карты объявления: map(100%) - tips_show(93%)-contacts_show(21%).
Выше всего конверсия в Целевое действие TIPS_CLICK(322)-> CONTACTS_SHOW(100) составляет - 31.06%.
На втором месте конверсия в Цeлевое действие search(1666) - contacts_show(377) составляет 22.63%.
На третьем месте конверсия в Цeлевое действие map(1456) - contacts_show(289) - 19.85%.
На четвертом месте конверсия в Цeлевое действие advert_open(751) - contacts_show(138) составляет 18.38%.
Между конверсиями по действиям: tips_show + tips_click и tips_show есть значимая разница: Конверсия в Целевое действие tips_show + tips_click(297)-> contacts_show(91) составляет - 30.64%. Конверсия в Целевое действие tips_show(2504) -> contacts_show(425) составляет - 16.97%.
Между конверсиями по источникам: yandex и google нет значимой разницы: Конверсия просмотры контактов по источникам yandex составляет 24.72%. Конверсия просмотры контактов по источникам google составляет 24.36%
Усовершенствовать алгоритм подбора рекомендованных объявлений. При настройке кампаний нужно составить объявления так, чтобы на них кликали. Для этого нужно соблюсти основные правила:
Рассмотреть возможность услуги по доставке товаров до покупателя. Для увеличения пользовательской активности сделать систему скидок по количеству покупаемых товаров. За оставленные положительные отзывы премировать в виде скидок и бесплатной доставки. Ежемесячно проводить рассылку пользователям по раннее интерисующим его товарам.